ASM核心API-方法(转)

2019-06-09  本文已影响0人  呵呵_9e25

ASM核心API-方法

2018年07月22日 21:04:41 伊布拉西莫 阅读数:776

方法结构-示例

类文件如下:

public class Bean {
    private int f;

    public int getF() {
        return f;
    }

    public void setF(int f) {
        this.f = f;
    }
}

使用javap -verbose xx/xx/Bean,查看字节码指令,观察方法区:

构造方法
如果程序没有显示的定义构造函、器,编译器会自动生成一个默认的构造器Bean(){super();}

0: aload_0                   #this
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: return

getF()方法
Xreturn,ireturn表示int, 如果是引用类型为areturn ,void则为return;

0: aload_0
1: getfield      #2                  // Field f:I
4: ireturn                  ###ireturn == return int ;

setF()方法

0: aload_0                  #### this
1: iload_1                  ### 入参 f;
2: putfield      #2                  // Field f:I
5: return     ###return void

抛出异常
增加一个稍微复杂的方法,抛出异常:

    public void checkAndSetF(int f) {
        if (f >= 0) {
            this.f = f;
        } else {
            throw new IllegalArgumentException();
        }
    }

字节码指令如下:

0: iload_1
1: iflt          12     ###如果栈顶值<=0,则跳转至label标记指定的指令,否则顺序执行
4: aload_0
5: iload_1
6: putfield      #2     // Field f:I
9: goto          20     ####无条件跳转
###创建一个异常对象,并压入栈顶。
12: new          #3     // class java/lang/IllegalArgumentException
15: dup                 ####栈顶值再入栈一次,此时栈顶前2位都是同一个值
###invokespecial 弹出栈顶元素,调用其构造函数,此时栈顶值仍然是异常对象
16: invokespecial #4    // Method java/lang/IllegalArgumentException."<init>":()V  
19: athrow    ###弹出剩下的异常的副本,
20: return

异常处理try-catch
增加try-catch方法:

public static void sleep(long d) {
    try {
        Thread.sleep(d);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

字节码指令如下:

0: lload_0
1: invokestatic  #5      // Method java/lang/Thread.sleep:(J)V
4: goto          20      ####正常执行至末尾return
7: astore_2
8: aload_2
9: invokevirtual #7     // Method java/lang/InterruptedException.printStackTrace:()V
12: goto          20
15: astore_2
16: aload_2
17: invokevirtual #9    // Method java/lang/Exception.printStackTrace:()V
20: return
Exception table:
###from-to偏移区间 出现type的异常,跳转至target
from    to  target type
  0     4     7   Class java/lang/InterruptedException
  0     4    15   Class java/lang/Exception

不存在用于捕获异常的字节代码: 而是将一个方法的字节代码与一个异常处理器列表关联在一起,这个列表规定了在某方法中一给定部分抛出异常时必须执行的代码。



MethodVisitor

用于生成和变转已编译方法的都是基于 MethodVisitor 抽象类的,它由 ClassVisitor.visitMethod()方法返回。

public abstract class MethodVisitor {
    AnnotationVisitor visitAnnotationDefault();
    AnnotationVisitor visitAnnotation(String desc, boolean visible);
    AnnotationVisitor visitParameterAnnotation(int parameter,String desc, boolean visible);
    void visitAttribute(Attribute attr);
    //..................
    void visitCode(); //方法字节码开始
//  void visitXXXInsn(int opcode);

    void visitLabel(Label label);
    void visitTryCatchBlock(Label start, Label end, Label handler,String type);

    void visitMaxs(int maxStack, int maxLocals); //设置局部变量,栈帧大小
    void visitEnd(); //方法字节码结束
}

使用MethodVisitor .visitXXXInsn()来填充函数,添加方法实现的字节码

MethodVisitor 类的方法必须按以下顺序调用(在这个类的 Javadoc 中规定):

ClassWriter 选项
MethodVisitor.visitMaxs(int ,int)为一个方法计算局部变量和栈帧大小并不是一件容易的事。好在在创建**ClassWriter **时,通过设置选项,使ASM为我们完成这些计算。


生成方法

普通方法

//普通方法 empty()
mv = cw.visitMethod(ACC_PRIVATE, "empty", "()V", null, null);

//静态static emptyStatic()
mv = cw.visitMethod(ACC_PRIVATE|ACC_STATIC, "emptyStatic", "()V", null, null);

//implements java.lang.AutoCloseable.close()
mv = cw.visitMethod(ACC_INTERFACE, "close", "()V", null, new String[]{"java/lang/Exception"});

运行结果:

private void empty() {
}

private static void emptyStatic() {
}

void close() throws Exception {
}

构造方法

调用构造函数时,需要调用父类的构造函数或者类内部其他构造函数。

无参构造方法

MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD,0); //visitVarInsn获取变量 this
mv.visitMethodInsn(INVOKESPECIAL,"java/lang/Thread","<init>","()V");//自动添加:调用父类的构造函数
mv.visitInsn(RETURN);
mv.visitMaxs(1,1);
mv.visitEnd();

有参构造方法
可以使用super()或者this():因为上面的无参的构造函数中调用了super()

mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/String;I)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD,0); // this
mv.visitMethodInsn(INVOKESPECIAL,clazzPath,"<init>","()V"); //调用自身的无参的构造函数,
mv.visitVarInsn(ALOAD,0); // this
mv.visitVarInsn(ALOAD,1); //获取入参 name
mv.visitFieldInsn(PUTFIELD,clazzPath,"name","Ljava/lang/String;"); 
mv.visitVarInsn(ALOAD,0); //
mv.visitVarInsn(ILOAD,2); //获取入参 age
mv.visitFieldInsn(PUTFIELD,clazzPath,"age","I");
mv.visitInsn(RETURN);
mv.visitMaxs(2,3);
mv.visitEnd();

调用结果

public User() {
    //如果没有显式的调用父类构造方法,会默认调用父类无参的构造函数super()
}

public User(String var1, int var2) {
    this(); //间接调用 无参构造函数 中的 super();
    this.name = var1;
    this.age = var2;
}

静态构造方法<clinit>

即静态语句块static {}

mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
mv.visitLdcInsn(1234L);
mv.visitMethodInsn(INVOKESTATIC,"java/lang/Long","valueOf","(J)Ljava/lang/Long;"); //Long.valueOf(long);自动装箱
mv.visitFieldInsn(PUTSTATIC,clazzPath,"clazzId","Ljava/lang/Long;");
mv.visitFieldInsn(GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");
mv.visitLdcInsn("static{} invoked...");
mv.visitMethodInsn(INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V");
mv.visitInsn(RETURN);
mv.visitMaxs(2,0); //long 占用2单位slot
mv.visitEnd();

期望运行结果

static {
    clazzId = 1234L;
    System.out.println("static{} invoked...");
}

实际运行结果:

public static Long clazzId = Long.valueOf(1234L);
static {
     System.out.println("static{} invoked...");
 }


转换方法

方法可以像类一样进行转换,也就是使用一个方法适配器将它收到的方法调用转发出去,并进行一些修改:

例:删除方法中的 NOP指令(因为它们不做任何事情,所以删除它们没有任何问题)
a).RemoveNopAdapter继承MethodVisitor

public class RemoveNopAdapter extends MethodVisitor {
    public RemoveNopAdapter(MethodVisitor mv) {
        super(ASM5,mv);
    }

    @Override
    public void visitInsn(int opcode) {
        if(opcode == NOP){
            return ;
        }
        super.visitInsn(opcode);
    }
}

b).RemoveNopClassAdapter继承ClassVisitor

public class RemoveNopClassAdapter extends ClassVisitor {
    public RemoveNopClassAdapter(ClassVisitor classVisitor) {
        super(ASM5, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        if (mv != null) {
            //调用MethodAdapter
            mv = new RemoveNopAdapter(mv);
        }
        return mv;
    }
}

此时,调用程序图如下:

这里写图片描述

例1:为类增加安全控制

public class Account {
    public void operation() {
        System.out.println("operation.....");
    }
}

目前Accout.operation()没有任何控制手段,现希望给operation()增加一些安全校验,判断这个方法是否有权限执行这个方法,如果有,则执行该方法;如果没有,直接退出。
增加安全校验方法:

public class SecurityChecker {
    public static boolean checkSecurity() {
        System.out.println("SecurityChecker.checkSecurity.....");
        /**
         * 简单模拟“安全校验”
         * 当前系统时间,尾数如果是偶数,则结果是1,如果是奇数则结果是0
         */
        if ((System.currentTimeMillis() & 0x1) == 0) {
            return false;
        } else {
            return true;
        }
    }
}

即将原有的Account转换为如下形式:

public class Account {
    public void operation() throws InterruptedException{
        if(SecurityChecker.checkSecurity()){
            System.out.println("operation.....");
        }   
    }
}

**方法适配器AddSecurityCheckMethodAdapter **

class AddSecurityCheckMethodAdapter extends MethodVisitor {

    public AddSecurityCheckMethodAdapter(MethodVisitor mv) {
        super(Opcodes.ASM5, mv);
    }

    @Override
    public void visitCode() {
        Label continueLabel = new Label();
        visitMethodInsn(INVOKESTATIC,"asm/security/SecurityChecker","checkSecurity","()Z");
        //IFNE i != 0 时跳转,即true的时候跳转;visitLabel-->continueLabel处,继续执行
        visitJumpInsn(IFNE,continueLabel); //如果checkSecurity=false,则不发生跳转,顺序执行,下一条指令直接返回
        visitInsn(RETURN);
        visitLabel(continueLabel);
        super.visitCode();
    }
}

类适配器TimeStatClassAdapter

class AddSecurityCheckClassAdapter extends ClassVisitor {

    public AddSecurityCheckClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        MethodVisitor wrappedMv = mv;
        //当遇到operation方法时,
        if(mv != null && name.equals("operation")){
            wrappedMv = new AddSecurityCheckMethodAdapter(mv);
        }
        return wrappedMv;
    }
}

例2:统计函数执行时间

统计Accout.operation()方法的执行时间:

public class Account {
    public void operation() throws InterruptedException{
        System.out.println("operation.....");
        Thread.sleep(new Random().nextInt(1000));
    }
}

我们需要统计每个方法花费的时间,需要加入如下统计功能:

public class TimeStat {
    static ThreadLocal<Long> t = new ThreadLocal<>();

    public static void start() {
        t.set(System.currentTimeMillis());
    }

    public static void end(){
        long end = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getStackTrace()+" spend:" +(end - t.get()));
    }
}

即将原有的Account转换为如下形式:

public class Account {
    public void operation() throws InterruptedException{
        TimeStat.start();
        System.out.println("operation.....");
        Thread.sleep(new Random().nextInt(1000));
        TimeStat.end();
    }
}

**方法适配器TimeStatMethodAdapter **

class TimeStatMethodAdapter extends MethodVisitor {

    public TimeStatMethodAdapter(MethodVisitor mv) {
        super(Opcodes.ASM5, mv);
    }

    @Override
    public void visitCode() {
        //方法执行开始,增加TimeStat.start();
        visitMethodInsn(INVOKESTATIC, "asm/timer/TimeStat", "start", "()V");
        super.visitCode();
    }

    @Override
    public void visitInsn(int opcode) {
    /*  int IRETURN = 172; // visitInsn
        int LRETURN = 173; // -
        int FRETURN = 174; // -
        int DRETURN = 175; // -
        int ARETURN = 176; // -
        int RETURN = 177; // -*/
        //方法返回前,增加TimeStat.end();
        if (opcode >= IRETURN && opcode <= RETURN) {
            visitMethodInsn(INVOKESTATIC, "asm/timer/TimeStat", "end", "()V");
        }
        super.visitInsn(opcode);
    }
}

类适配器TimeStatClassAdapter

class TimeStatClassAdapter extends ClassVisitor {

    public TimeStatClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        MethodVisitor wrappedMv = mv;
        //判断,如果是operation方法时,使用适配方法
        if (mv != null && name.equals("operation")) {
            wrappedMv = new TimeStatMethodAdapter(mv);
        }
        return wrappedMv;
    }
}

工具

public MethodVisitor visitMethod(int access, String name,String desc, String signature, String[] exceptions) {
    MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);
    if (debug && mv != null && ...) { // 如果必须跟踪此方法
        Printer p = new Textifier(ASM5) {
            @Override 
            public void visitMethodEnd() {
                print(aPrintWriter); // 在其被访问后输出它
            }
        };
        mv = new TraceMethodVisitor(mv, p);
    }
    return new MyMethodAdapter(mv);
}

LocalVariablesSorter
这个方法适配器将一个方法中使用的局部变量按照它们在这个方法中的出现顺序重新进行编号。在向一个方法中插入新的局部变量时,这个适配器很有用。没有这个适配器,就需要在所有已有局部变量之后添加新的局部变量,但遗憾的是,在 visitMaxs 中,要直到方法的末尾处才能知道这些局部变量的编号。
假设需要一个这样局部变量来记录方法的执行时间:

public class Account  {
        public static long timer;
    public void operation() throws Exception {
        long t = System.currentTimeMillis();
        System.out.println("operation.....");
        Thread.sleep(100);
        timer += System.currentTimeMillis() - t;
    }
}

那么MethodAdapter

class TimeStatMethodAdapter2 extends MethodVisitor {
    private LocalVariablesSorter lvs;
    private AnalyzerAdapter aa;
    private String owner;
    private int time;
    private int maxStack;

    public TimeStatMethodAdapter2(String owner,MethodVisitor mv,LocalVariablesSorter lvs , AnalyzerAdapter aa) {
        super(ASM5, mv);
        this.owner = owner;
        this.lvs = lvs;//由于java是单继承,所以这里set
        this.aa = aa;
    }
    @Override public void visitCode() {
        mv.visitCode();
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System","currentTimeMillis", "()J");
        time = lvs.newLocal(Type.LONG_TYPE); //newLocal()
        mv.visitVarInsn(LSTORE, time);
        maxStack = 4;
    }

    @Override public void visitInsn(int opcode) {
        if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System","currentTimeMillis", "()J");
            mv.visitVarInsn(LLOAD, time);
            mv.visitInsn(LSUB); //sub
            mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
            mv.visitInsn(LADD);
            mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
            maxStack = Math.max(aa.stack.size() + 4, maxStack);
        }
        mv.visitInsn(opcode);
    }

    @Override
    public void visitMaxs(int maxStack, int maxLocals) {
        mv.visitMaxs(Math.max(this.maxStack, maxStack), maxLocals);
    }
}

AdviceAdapter
这个方法适配器是一个抽象类, 可用于在一个方法的开头以及恰在任意 RETURN 或 ATHROW指令之前插入代码。

public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes {
    protected void onMethodEnter(){}

    protected void onMethodExit(int opcode) {};
}

</article>

ASM核心API-方法

2018年07月22日 21:04:41 伊布拉西莫 阅读数:776

<article class="baidu_pl" style="font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; box-sizing: inherit; outline: 0px; margin: 0px; padding: 16px 0px 0px; display: block; position: relative; caret-color: rgba(0, 0, 0, 0.74902); color: rgba(0, 0, 0, 0.74902); font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif; font-size: 14px; background-color: rgb(255, 255, 255);">

方法结构-示例

类文件如下:

public class Bean {
    private int f;

    public int getF() {
        return f;
    }

    public void setF(int f) {
        this.f = f;
    }
}

使用javap -verbose xx/xx/Bean,查看字节码指令,观察方法区:

构造方法
如果程序没有显示的定义构造函、器,编译器会自动生成一个默认的构造器Bean(){super();}

0: aload_0                   #this
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: return

getF()方法
Xreturn,ireturn表示int, 如果是引用类型为areturn ,void则为return;

0: aload_0
1: getfield      #2                  // Field f:I
4: ireturn                  ###ireturn == return int ;

setF()方法

0: aload_0                  #### this
1: iload_1                  ### 入参 f;
2: putfield      #2                  // Field f:I
5: return     ###return void

抛出异常
增加一个稍微复杂的方法,抛出异常:

    public void checkAndSetF(int f) {
        if (f >= 0) {
            this.f = f;
        } else {
            throw new IllegalArgumentException();
        }
    }

字节码指令如下:

0: iload_1
1: iflt          12     ###如果栈顶值<=0,则跳转至label标记指定的指令,否则顺序执行
4: aload_0
5: iload_1
6: putfield      #2     // Field f:I
9: goto          20     ####无条件跳转
###创建一个异常对象,并压入栈顶。
12: new          #3     // class java/lang/IllegalArgumentException
15: dup                 ####栈顶值再入栈一次,此时栈顶前2位都是同一个值
###invokespecial 弹出栈顶元素,调用其构造函数,此时栈顶值仍然是异常对象
16: invokespecial #4    // Method java/lang/IllegalArgumentException."<init>":()V  
19: athrow    ###弹出剩下的异常的副本,
20: return

异常处理try-catch
增加try-catch方法:

public static void sleep(long d) {
    try {
        Thread.sleep(d);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

字节码指令如下:

0: lload_0
1: invokestatic  #5      // Method java/lang/Thread.sleep:(J)V
4: goto          20      ####正常执行至末尾return
7: astore_2
8: aload_2
9: invokevirtual #7     // Method java/lang/InterruptedException.printStackTrace:()V
12: goto          20
15: astore_2
16: aload_2
17: invokevirtual #9    // Method java/lang/Exception.printStackTrace:()V
20: return
Exception table:
###from-to偏移区间 出现type的异常,跳转至target
from    to  target type
  0     4     7   Class java/lang/InterruptedException
  0     4    15   Class java/lang/Exception

不存在用于捕获异常的字节代码: 而是将一个方法的字节代码与一个异常处理器列表关联在一起,这个列表规定了在某方法中一给定部分抛出异常时必须执行的代码。



MethodVisitor

用于生成和变转已编译方法的都是基于 MethodVisitor 抽象类的,它由 ClassVisitor.visitMethod()方法返回。

public abstract class MethodVisitor {
    AnnotationVisitor visitAnnotationDefault();
    AnnotationVisitor visitAnnotation(String desc, boolean visible);
    AnnotationVisitor visitParameterAnnotation(int parameter,String desc, boolean visible);
    void visitAttribute(Attribute attr);
    //..................
    void visitCode(); //方法字节码开始
//  void visitXXXInsn(int opcode);

    void visitLabel(Label label);
    void visitTryCatchBlock(Label start, Label end, Label handler,String type);

    void visitMaxs(int maxStack, int maxLocals); //设置局部变量,栈帧大小
    void visitEnd(); //方法字节码结束
}

使用MethodVisitor .visitXXXInsn()来填充函数,添加方法实现的字节码

MethodVisitor 类的方法必须按以下顺序调用(在这个类的 Javadoc 中规定):

ClassWriter 选项
MethodVisitor.visitMaxs(int ,int)为一个方法计算局部变量和栈帧大小并不是一件容易的事。好在在创建**ClassWriter **时,通过设置选项,使ASM为我们完成这些计算。


生成方法

普通方法

//普通方法 empty()
mv = cw.visitMethod(ACC_PRIVATE, "empty", "()V", null, null);

//静态static emptyStatic()
mv = cw.visitMethod(ACC_PRIVATE|ACC_STATIC, "emptyStatic", "()V", null, null);

//implements java.lang.AutoCloseable.close()
mv = cw.visitMethod(ACC_INTERFACE, "close", "()V", null, new String[]{"java/lang/Exception"});

运行结果:

private void empty() {
}

private static void emptyStatic() {
}

void close() throws Exception {
}

构造方法

调用构造函数时,需要调用父类的构造函数或者类内部其他构造函数。

无参构造方法

MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD,0); //visitVarInsn获取变量 this
mv.visitMethodInsn(INVOKESPECIAL,"java/lang/Thread","<init>","()V");//自动添加:调用父类的构造函数
mv.visitInsn(RETURN);
mv.visitMaxs(1,1);
mv.visitEnd();

有参构造方法
可以使用super()或者this():因为上面的无参的构造函数中调用了super()

mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/String;I)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD,0); // this
mv.visitMethodInsn(INVOKESPECIAL,clazzPath,"<init>","()V"); //调用自身的无参的构造函数,
mv.visitVarInsn(ALOAD,0); // this
mv.visitVarInsn(ALOAD,1); //获取入参 name
mv.visitFieldInsn(PUTFIELD,clazzPath,"name","Ljava/lang/String;"); 
mv.visitVarInsn(ALOAD,0); //
mv.visitVarInsn(ILOAD,2); //获取入参 age
mv.visitFieldInsn(PUTFIELD,clazzPath,"age","I");
mv.visitInsn(RETURN);
mv.visitMaxs(2,3);
mv.visitEnd();

调用结果

public User() {
    //如果没有显式的调用父类构造方法,会默认调用父类无参的构造函数super()
}

public User(String var1, int var2) {
    this(); //间接调用 无参构造函数 中的 super();
    this.name = var1;
    this.age = var2;
}

静态构造方法<clinit>

即静态语句块static {}

mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
mv.visitLdcInsn(1234L);
mv.visitMethodInsn(INVOKESTATIC,"java/lang/Long","valueOf","(J)Ljava/lang/Long;"); //Long.valueOf(long);自动装箱
mv.visitFieldInsn(PUTSTATIC,clazzPath,"clazzId","Ljava/lang/Long;");
mv.visitFieldInsn(GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");
mv.visitLdcInsn("static{} invoked...");
mv.visitMethodInsn(INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V");
mv.visitInsn(RETURN);
mv.visitMaxs(2,0); //long 占用2单位slot
mv.visitEnd();

期望运行结果

static {
    clazzId = 1234L;
    System.out.println("static{} invoked...");
}

实际运行结果:

public static Long clazzId = Long.valueOf(1234L);
static {
     System.out.println("static{} invoked...");
 }


转换方法

方法可以像类一样进行转换,也就是使用一个方法适配器将它收到的方法调用转发出去,并进行一些修改:

例:删除方法中的 NOP指令(因为它们不做任何事情,所以删除它们没有任何问题)
a).RemoveNopAdapter继承MethodVisitor

public class RemoveNopAdapter extends MethodVisitor {
    public RemoveNopAdapter(MethodVisitor mv) {
        super(ASM5,mv);
    }

    @Override
    public void visitInsn(int opcode) {
        if(opcode == NOP){
            return ;
        }
        super.visitInsn(opcode);
    }
}

b).RemoveNopClassAdapter继承ClassVisitor

public class RemoveNopClassAdapter extends ClassVisitor {
    public RemoveNopClassAdapter(ClassVisitor classVisitor) {
        super(ASM5, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        if (mv != null) {
            //调用MethodAdapter
            mv = new RemoveNopAdapter(mv);
        }
        return mv;
    }
}

此时,调用程序图如下:

这里写图片描述

例1:为类增加安全控制

public class Account {
    public void operation() {
        System.out.println("operation.....");
    }
}

目前Accout.operation()没有任何控制手段,现希望给operation()增加一些安全校验,判断这个方法是否有权限执行这个方法,如果有,则执行该方法;如果没有,直接退出。
增加安全校验方法:

public class SecurityChecker {
    public static boolean checkSecurity() {
        System.out.println("SecurityChecker.checkSecurity.....");
        /**
         * 简单模拟“安全校验”
         * 当前系统时间,尾数如果是偶数,则结果是1,如果是奇数则结果是0
         */
        if ((System.currentTimeMillis() & 0x1) == 0) {
            return false;
        } else {
            return true;
        }
    }
}

即将原有的Account转换为如下形式:

public class Account {
    public void operation() throws InterruptedException{
        if(SecurityChecker.checkSecurity()){
            System.out.println("operation.....");
        }   
    }
}

**方法适配器AddSecurityCheckMethodAdapter **

class AddSecurityCheckMethodAdapter extends MethodVisitor {

    public AddSecurityCheckMethodAdapter(MethodVisitor mv) {
        super(Opcodes.ASM5, mv);
    }

    @Override
    public void visitCode() {
        Label continueLabel = new Label();
        visitMethodInsn(INVOKESTATIC,"asm/security/SecurityChecker","checkSecurity","()Z");
        //IFNE i != 0 时跳转,即true的时候跳转;visitLabel-->continueLabel处,继续执行
        visitJumpInsn(IFNE,continueLabel); //如果checkSecurity=false,则不发生跳转,顺序执行,下一条指令直接返回
        visitInsn(RETURN);
        visitLabel(continueLabel);
        super.visitCode();
    }
}

类适配器TimeStatClassAdapter

class AddSecurityCheckClassAdapter extends ClassVisitor {

    public AddSecurityCheckClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        MethodVisitor wrappedMv = mv;
        //当遇到operation方法时,
        if(mv != null && name.equals("operation")){
            wrappedMv = new AddSecurityCheckMethodAdapter(mv);
        }
        return wrappedMv;
    }
}

例2:统计函数执行时间

统计Accout.operation()方法的执行时间:

public class Account {
    public void operation() throws InterruptedException{
        System.out.println("operation.....");
        Thread.sleep(new Random().nextInt(1000));
    }
}

我们需要统计每个方法花费的时间,需要加入如下统计功能:

public class TimeStat {
    static ThreadLocal<Long> t = new ThreadLocal<>();

    public static void start() {
        t.set(System.currentTimeMillis());
    }

    public static void end(){
        long end = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getStackTrace()+" spend:" +(end - t.get()));
    }
}

即将原有的Account转换为如下形式:

public class Account {
    public void operation() throws InterruptedException{
        TimeStat.start();
        System.out.println("operation.....");
        Thread.sleep(new Random().nextInt(1000));
        TimeStat.end();
    }
}

**方法适配器TimeStatMethodAdapter **

class TimeStatMethodAdapter extends MethodVisitor {

    public TimeStatMethodAdapter(MethodVisitor mv) {
        super(Opcodes.ASM5, mv);
    }

    @Override
    public void visitCode() {
        //方法执行开始,增加TimeStat.start();
        visitMethodInsn(INVOKESTATIC, "asm/timer/TimeStat", "start", "()V");
        super.visitCode();
    }

    @Override
    public void visitInsn(int opcode) {
    /*  int IRETURN = 172; // visitInsn
        int LRETURN = 173; // -
        int FRETURN = 174; // -
        int DRETURN = 175; // -
        int ARETURN = 176; // -
        int RETURN = 177; // -*/
        //方法返回前,增加TimeStat.end();
        if (opcode >= IRETURN && opcode <= RETURN) {
            visitMethodInsn(INVOKESTATIC, "asm/timer/TimeStat", "end", "()V");
        }
        super.visitInsn(opcode);
    }
}

类适配器TimeStatClassAdapter

class TimeStatClassAdapter extends ClassVisitor {

    public TimeStatClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
        MethodVisitor wrappedMv = mv;
        //判断,如果是operation方法时,使用适配方法
        if (mv != null && name.equals("operation")) {
            wrappedMv = new TimeStatMethodAdapter(mv);
        }
        return wrappedMv;
    }
}

工具

public MethodVisitor visitMethod(int access, String name,String desc, String signature, String[] exceptions) {
    MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);
    if (debug && mv != null && ...) { // 如果必须跟踪此方法
        Printer p = new Textifier(ASM5) {
            @Override 
            public void visitMethodEnd() {
                print(aPrintWriter); // 在其被访问后输出它
            }
        };
        mv = new TraceMethodVisitor(mv, p);
    }
    return new MyMethodAdapter(mv);
}

LocalVariablesSorter
这个方法适配器将一个方法中使用的局部变量按照它们在这个方法中的出现顺序重新进行编号。在向一个方法中插入新的局部变量时,这个适配器很有用。没有这个适配器,就需要在所有已有局部变量之后添加新的局部变量,但遗憾的是,在 visitMaxs 中,要直到方法的末尾处才能知道这些局部变量的编号。
假设需要一个这样局部变量来记录方法的执行时间:

public class Account  {
        public static long timer;
    public void operation() throws Exception {
        long t = System.currentTimeMillis();
        System.out.println("operation.....");
        Thread.sleep(100);
        timer += System.currentTimeMillis() - t;
    }
}

那么MethodAdapter

class TimeStatMethodAdapter2 extends MethodVisitor {
    private LocalVariablesSorter lvs;
    private AnalyzerAdapter aa;
    private String owner;
    private int time;
    private int maxStack;

    public TimeStatMethodAdapter2(String owner,MethodVisitor mv,LocalVariablesSorter lvs , AnalyzerAdapter aa) {
        super(ASM5, mv);
        this.owner = owner;
        this.lvs = lvs;//由于java是单继承,所以这里set
        this.aa = aa;
    }
    @Override public void visitCode() {
        mv.visitCode();
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System","currentTimeMillis", "()J");
        time = lvs.newLocal(Type.LONG_TYPE); //newLocal()
        mv.visitVarInsn(LSTORE, time);
        maxStack = 4;
    }

    @Override public void visitInsn(int opcode) {
        if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System","currentTimeMillis", "()J");
            mv.visitVarInsn(LLOAD, time);
            mv.visitInsn(LSUB); //sub
            mv.visitFieldInsn(GETSTATIC, owner, "timer", "J");
            mv.visitInsn(LADD);
            mv.visitFieldInsn(PUTSTATIC, owner, "timer", "J");
            maxStack = Math.max(aa.stack.size() + 4, maxStack);
        }
        mv.visitInsn(opcode);
    }

    @Override
    public void visitMaxs(int maxStack, int maxLocals) {
        mv.visitMaxs(Math.max(this.maxStack, maxStack), maxLocals);
    }
}

AdviceAdapter
这个方法适配器是一个抽象类, 可用于在一个方法的开头以及恰在任意 RETURN 或 ATHROW指令之前插入代码。

public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes {
    protected void onMethodEnter(){}

    protected void onMethodExit(int opcode) {};
}

</article>

上一篇下一篇

猜你喜欢

热点阅读