美文网首页
简单介绍ASM核心API

简单介绍ASM核心API

作者: 勇敢地追 | 来源:发表于2019-12-30 18:11 被阅读0次

    核心类是 ClassVisitor

    public abstract class ClassVisitor {
        public ClassVisitor(int api) {}
        public ClassVisitor(int api, ClassVisitor classVisitor) {}
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {}
        public void visitSource(String source, String debug) {}
        public ModuleVisitor visitModule(String name, int access, String version) {}
        public void visitNestHost(String nestHost) {}
        public void visitOuterClass(String owner, String name, String descriptor) {}
        public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {}
        public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {}
        public void visitAttribute(Attribute attribute) {}
        public void visitNestMember(String nestMember) {}
        public void visitInnerClass(String name, String outerName, String innerName, int access) {}
        public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {}
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {}
        public void visitEnd() {}
    }
    

    它的调用顺序是首先调用 visit,然后是对 visitSource 的最多一个调用,接下来是对 visitOuterClass 的最多一个调用,然后是可按任意顺序对 visitAnnotation 和 visitAttribute 的任意多个访问,接下来是可按任意顺序对 visitInnerClass、 visitField 和 visitMethod 的任意多个调用,最后以一个 visitEnd 调用结束。

    ASM 提供了三个基于 ClassVisitor API 的核心组件,用于生成和变化类:

    • ClassReader类分析以字节数组形式给出的已编译类,并针对在其accept方法参数中传送的ClassVisitor实例,调用相应的visitXxx方法。这个类可以看作一个事件产生器。
    • ClassWriter类是ClassVisitor抽象类的一个子类,它直接以二进制形式生成编译后的类。它会生成一个字节数组形式的输出,其中包含了已编译类,可以用toByteArray方法来􏰁取。这个类可以看作一个事件使用器。
    • ClassVisitor类将它收到的所有方法调用都委托给另一个ClassVisitor类。这个类可以看作一个事件筛选器。

    读取类

    在分析一个已经存在的类时,惟一必需的组件是ClassReader组件.以下内容就是用来打印一个类的内容的(简单化了的)

    public class ClassPrinter extends ClassVisitor {
        public ClassPrinter() {
            super(ASM7);
        }
    
        public ClassPrinter(ClassVisitor cv) {
            super(ASM7, cv);
        }
    
        @Override
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            super.visit(version, access, name, signature, superName, interfaces);
            Log.e("ClassVisitor", name + " extends " + superName + " {");
        }
    
        @Override
        public void visitSource(String source, String debug) {
            super.visitSource(source, debug);
        }
    
        @Override
        public ModuleVisitor visitModule(String name, int access, String version) {
            return super.visitModule(name, access, version);
        }
    
        @Override
        public void visitOuterClass(String owner, String name, String desc) {
            super.visitOuterClass(owner, name, desc);
        }
    
        @Override
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            return super.visitAnnotation(desc, visible);
        }
    
        @Override
        public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
            return super.visitTypeAnnotation(typeRef, typePath, desc, visible);
        }
    
        @Override
        public void visitAttribute(Attribute attr) {
            super.visitAttribute(attr);
        }
    
        @Override
        public void visitInnerClass(String name, String outerName, String innerName, int access) {
            super.visitInnerClass(name, outerName, innerName, access);
        }
    
        @Override
        public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
            Log.e("ClassVisitor", " " + desc + " " + name);
            return super.visitField(access, name, desc, signature, value);
        }
    
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            Log.e("ClassVisitor", " " + name + desc);
            return super.visitMethod(access, name, desc, signature, exceptions);
        }
    
        @Override
        public void visitEnd() {
            super.visitEnd();
            Log.e("ClassVisitor", "}");
        }
    }
    

    接下来可以将这个 ClassPrinter 与一个 ClassReader 组件合并在一起,使 ClassReader 产生的事件由我们的 ClassPrinter 使用

            ClassPrinter cp = new ClassPrinter();
            ClassReader cr = new ClassReader(Runnable.class.getName()); 
            cr.accept(cp, 0);
    

    这是java的写法.注意ClassReader构造函数,Android写法不一样.Android的apk里面不会自己带有Runnable.class文件,所以需要如下做

            ClassPrinter cp = new ClassPrinter();
            File srcFile = new File(Environment.getExternalStorageDirectory() + "/TestRunnable.class");
            ClassReader cr = new ClassReader(new FileInputStream(srcFile));
            cr.accept(cp, 0);
    

    生成类

            //写文件
            String className = "com/example/XXX/asmdemo/Comparable";//最后一个挨着class名字必须是斜杠,否则生成的class文件不会默认import
            ClassWriter cw = new ClassWriter(0);
            cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
                    className, null, "java/lang/Object", null);
            cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I", null, new Integer(-1)).visitEnd();
            cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I", null, new Integer(0)).visitEnd();
            cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I", null, new Integer(1)).visitEnd();
            cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I", null, null).visitEnd();
            cw.visitEnd();
            byte[] b = cw.toByteArray();
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                File sdCardDir = Environment.getExternalStorageDirectory();
                File saveFile = new File(sdCardDir, "Comparable.class");//必须是.class结尾,否则放IDE里面是乱码
                FileOutputStream outStream = new FileOutputStream(saveFile);
                outStream.write(b);
                outStream.close();
            }
    

    最终把生成的class文件adb pull出来放AS里面查看如下

    package com.example.XXX.asmdemo;
    public interface Comparable {
        int LESS = -1;
        int EQUAL = 0;
        int GREATER = 1;
        int compareTo(Object var1);
    }
    

    转化类

    可以通过ClassVisitor进行属性转化

            byte[] b1 = cw.toByteArray();
    
            ClassReader cr = new ClassReader(b1);
            ClassWriter cw2 = new ClassWriter(0);//优化方法,加入cr------ClassWriter(cr,0)
            // cv 将所有事件转发给 cw
            ChangeVersionAdapter ca = new ChangeVersionAdapter(cw2);
            cr.accept(ca, 0);
            byte[] b2 = cw.toByteArray(); // b2 与 b1 表示同一个类
    
        public class ChangeVersionAdapter extends ClassVisitor {
            public ChangeVersionAdapter(ClassVisitor cv) {
                super(ASM7, cv);
            }
    
            @Override
            public void visit(int version, int access, String name,
                              String signature, String superName, String[] interfaces) {
                cv.visit(V1_7, access, name, signature, superName, interfaces);//就修改了java版本号
            }
        }
    

    但这么做有个问题,上面的流程中整个 b1 均被分析,并 利用相应的事件从头从头构建了 b2,这种做法的效率不是很高。如果将 b1 中不被转换的部分 直接复制到 b2 中,不对其分析,也不生成相应的事件,其效率就会高得多

    优化方法

    在ClassReader组件的accept方法参数中传送了ClassVisitor,如果ClassReader检测到这个ClassVisitor返回的MethodVisitor来自一个ClassWriter,这意味着这个方法的内容将不会被转换,事实上,应用程序甚至不会看到其内容。在这种情况下,ClassReader组件不会分析这个方法的内容,不会生成相应事件,只是复制 ClassWriter中表示这个方法的字节数组。
    缺点:对于那些增加字段、方法或指令的转换来说,这一点不 成问题,但对于那些要移除或重命名许多类成员的转换来说,这一优化将导致类文件大于未优化 时的情况。因此,仅对“增加性”转换应用这一优化。

    移除类成员

    public class RemoveMemberAdapter extends ClassVisitor {
        private String name;
        private String desc;
    
        public RemoveMemberAdapter(int api, String name, String desc) {
            super(api);
            this.name = name;
            this.desc = desc;
        }
    
        public RemoveMemberAdapter(int api, ClassVisitor classVisitor, String name, String desc) {
            super(api, classVisitor);
            this.name = name;
            this.desc = desc;
        }
    
        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            if (this.name.equals(name) && this.desc.equals(descriptor)) {
                // 不要委托至下一个访问器 -> 这样将移除该方法
                return null;
            }
            return super.visitMethod(access, name, descriptor, signature, exceptions);
        }
    }
    

    增加类成员

    如果要向一个类中添加一个字段,必须在原方法调用之间添加对 visitField 的一 个新调用,而且必须将这个新调用放在类适配器的一个访问方法中。比如,不能在 visit 方法 中这样做,因为这样可能会导致对 visitField 的调用之后跟有 visitSource、 visitOuterClass、visitAnnotation 或 visitAttribute,这是无效的。出于同样的原因,不能将这个新调用放在 visitSource、visitOuterClass、visitAnnotation 或 visitAttribute 方法中. 仅有的可能位置是 visitInnerClass、visitField、 visitMethod 或 visitEnd 方法。
    事实上,惟一真正正确的解决方案是在 visitEnd 方法中添加更多调用,以添加新成员。实际上, 一个类中不得包含重复成员,要确保一个新成员没有重复成员,惟一方法就是将它与所有已有成员进行对 比,只有在 visitEnd 方法中访问了所有这些成员后才能完成这一工作。这种做法是相当受限制的。在 实践中,使用程序员不大可能使用的生成名,比如_counter$或4B7F i就足以避免重复成员了, 并不需要将它们添加到 visitEnd 中。

    public class AddFieldAdapter extends ClassVisitor {
        private ClassVisitor classVisitor;
        private int fAcc;
        private String fName;
        private String fDesc;
        private boolean isFieldPresent;
    
        public AddFieldAdapter(ClassVisitor classVisitor, int fAcc, String fName, String fDesc) {
            super(ASM7);
            this.classVisitor = classVisitor;
            this.fAcc = fAcc;
            this.fName = fName;
            this.fDesc = fDesc;
        }
    
        @Override
        public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
            if (name.equals(fName) && this.fDesc.equals(descriptor)) {
                isFieldPresent = true;
            }
            return classVisitor.visitField(access, name, descriptor, signature, value);
        }
    
        @Override
        public void visitEnd() {
            if (!isFieldPresent) {
                FieldVisitor fv = cv.visitField(fAcc, fName, fDesc, null, null);
                if (fv != null) {
                    fv.visitEnd();
                }
            }
            classVisitor.visitEnd();
        }
    }
    

    谈谈MethodVisitor

    下面将介绍一下如何通过MethodVisitor来生成一个方法.有如下一个类

        package pkg;
        public class Bean {
            private int f;
            public int getF() {
                return this.f; 
            }
        }
    

    getter 方法的字节代码为:

        ALOAD 0
        GETFIELD pkg/Bean f I
        IRETURN
    

    那么生成getter方法的步骤如下

    mv.visitCode();
    mv.visitVarInsn(ALOAD, 0); 
    mv.visitFieldInsn(GETFIELD, "pkg/Bean", "f", "I"); 
    mv.visitInsn(IRETURN);
    mv.visitMaxs(1, 1);
    mv.visitEnd();
    

    第一个调用启动字节代码的生成过程。然后是三个调用,生成这一方法的三条指令(可以看出,字节代码与 ASM API 之间的映射非常简单)。对 visitMaxs 的调用必须在已经访问了所有指令后执行。它用于为这个方法的执行帧定义局部变量和操作数栈部分的大小。最后一次调用用于结束此方法的生成过程。
    为什么visitMaxs分别是1呢?这个和基于栈的指令集有关,相关内容参考
    深入理解 JAVA 虚拟机(十)基于栈的字节码解释执行引擎
    Java字节码的介绍

    相关文章

      网友评论

          本文标题:简单介绍ASM核心API

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