美文网首页
ASM核心API-方法(转)

ASM核心API-方法(转)

作者: 呵呵_9e25 | 来源:发表于2019-06-09 10:52 被阅读0次

    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()来填充函数,添加方法实现的字节码

    • visitVarInsn(int opcode, int var) :带有参数的字节码指令
    • visitInsn(int opcode) : 无参数的字节码指令
    • visitLdcInsn(Object cst): LDC专用指令。LDC_W,LDC2_W已被废弃
    • visitTypeInsn(int opcode, String type) :带有引用类型参数的字节码指令
    • visitMethodInsn(int opcode, String owner, String name,String desc):调用方法
    • visitFieldInsn(int opcode, String owner, String name, String desc):操作变量

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

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

    • 在使用 new ClassWriter(0)时,不会自动计算任何东西。必须自行计算帧、局部变量与操作数栈的大小。
    • 在使用 new ClassWriter(ClassWriter.COMPUTE_MAXS)时,将为你计算局部变量与操作数栈部分的大小。还是必须调用 visitMaxs,但可以使用任何参数:它们将被忽略并重新计算。使用这一选项时,仍然必须自行计算这些帧。
    • 在使用 new ClassWriter(ClassWriter.COMPUTE_FRAMES)时,一切都是自动计算。不再需要调用 visitFrame,但仍然必须调用 visitMaxs(参数将被忽略并重新计算)。

    生成方法

    普通方法

    • 普通方法的入参中第一个参数是this。也就是说它的args_size最小是1,而静态方法是0
    • 定义静态方法必须要ACC_STATIC
    • 调用普通方法INVOKEVIRTUAL,调用static方法:INVOKESTATIC
    //普通方法 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;
        }
    }
    

    工具

    • Type:t.getOpcode(IMUL),若 t 等于 Type.FLOAT_TYPE,则返回 FMUL
    • TraceClassVisitor它打印它所访问类的文本表示, 包括类的方法的文本表示,可以使用TraceMethodVisitor代替TraceClassVisitor
    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);
    }
    
    • CheckClassAdapter来检验ClassVisitor 方法的调用顺序是否适当,参数是否有效,与之类似的,CheckMethodAdapter来检查一个方法,而不是检查整个类。
    • AnalyzerAdapter:这个方法适配器根据 visitFrame 中访问的帧,计算每条指令之前的栈映射帧。

    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()来填充函数,添加方法实现的字节码

    • visitVarInsn(int opcode, int var) :带有参数的字节码指令
    • visitInsn(int opcode) : 无参数的字节码指令
    • visitLdcInsn(Object cst): LDC专用指令。LDC_W,LDC2_W已被废弃
    • visitTypeInsn(int opcode, String type) :带有引用类型参数的字节码指令
    • visitMethodInsn(int opcode, String owner, String name,String desc):调用方法
    • visitFieldInsn(int opcode, String owner, String name, String desc):操作变量

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

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

    • 在使用 new ClassWriter(0)时,不会自动计算任何东西。必须自行计算帧、局部变量与操作数栈的大小。
    • 在使用 new ClassWriter(ClassWriter.COMPUTE_MAXS)时,将为你计算局部变量与操作数栈部分的大小。还是必须调用 visitMaxs,但可以使用任何参数:它们将被忽略并重新计算。使用这一选项时,仍然必须自行计算这些帧。
    • 在使用 new ClassWriter(ClassWriter.COMPUTE_FRAMES)时,一切都是自动计算。不再需要调用 visitFrame,但仍然必须调用 visitMaxs(参数将被忽略并重新计算)。

    生成方法

    普通方法

    • 普通方法的入参中第一个参数是this。也就是说它的args_size最小是1,而静态方法是0
    • 定义静态方法必须要ACC_STATIC
    • 调用普通方法INVOKEVIRTUAL,调用static方法:INVOKESTATIC
    //普通方法 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;
        }
    }
    

    工具

    • Type:t.getOpcode(IMUL),若 t 等于 Type.FLOAT_TYPE,则返回 FMUL
    • TraceClassVisitor它打印它所访问类的文本表示, 包括类的方法的文本表示,可以使用TraceMethodVisitor代替TraceClassVisitor
    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);
    }
    
    • CheckClassAdapter来检验ClassVisitor 方法的调用顺序是否适当,参数是否有效,与之类似的,CheckMethodAdapter来检查一个方法,而不是检查整个类。
    • AnalyzerAdapter:这个方法适配器根据 visitFrame 中访问的帧,计算每条指令之前的栈映射帧。

    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-方法(转)

          本文链接:https://www.haomeiwen.com/subject/zjugxctx.html