简单介绍ASM核心API

2019-12-30  本文已影响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组件.以下内容就是用来打印一个类的内容的(简单化了的)

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字节码的介绍

上一篇下一篇

猜你喜欢

热点阅读