ASM基础介绍

1,503 阅读3分钟

ASM是Java字节码操作与分析框架,我们可以动态生成类或者增强既有类的功能。它可以直接生成二进制.class文件,也可以在类被加入Java虚拟机之前动态的改变现有类的行为。ASM框架中几个核心类如下:

  • ClassReader:解析编译过的.class字节码文件
  • ClassWriter:重新构建编译后的类,比如修改类名,属性及方法,甚至可以生成新的类字节码文件
  • ClassVisitor:主要负责“访问”类成员信息,包括在类上的注解,构造方法,类的字段,类的方法,静态代码块等
  • AdviceAdapter:实现了MethodVisitor接口,主要负责“访问”方法的信息,用来进行具体的方法字节码操作 ClassVisitor的主要方法介绍如下:
class MyClassVisitor extends ClassVisitor {
    MyClassVisitor(ClassVisitor classVisitor) {
        //参数为ASM版本号,classVisitor
        super(Opcodes.ASM5, classVisitor)
    }
    /**
     * 可以拿到类的全部信息,然后对满足条件的类进行过滤,遍历类开始的时候调用该方法
     * @param version:JDK的版本
     * @param access:类的修饰符,它是以ACC_开头的常量,有ACC_PUBLIC(public),ACC_PRIVATE(private),ACC_PROTECTED(protected),
     *                ACC_FINAL(final),ACC_SUPER(extends),ACC_INTERFACE(接口),ACC_ABSTRACT(抽象类),ACC_ANNOTATION(注解类型)
     *                ACC_ENUM(枚举类型),ACC_DEPRECATED(标记了@Deprecated的类),ACC_SYNTHETIC(Java生成)
     * @param name:类的名称。它是以路径的形式显示的(a.b.c.MyClass的显示为a/b/c/MyClass,并且使不带".class"后缀的)
     * @param signature:泛型信息,如果未定义任何泛型,则参数为空
     * @param superName:当前类所继承的父类
     * @param interfaces:该类所实现的接口列表。因为一个类可以实现多个不同的接口,所以是个数组
     */
    @Override
    void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces)
    }
    /**
     * 访问内部类
     * @param name
     * @param outerName
     * @param innerName
     * @param access
     */
    @Override
    void visitInnerClass(String name, String outerName, String innerName, int access) {
        super.visitInnerClass(name, outerName, innerName, access)
    }
    /**
     * 拿到要修改的方法,然后进行修改操作,扫描器扫描到类的方法时会调用
     * @param access:表示方法的修饰符。也是以ACC_开头的常量。除了和类共用的那些public,private等,方法中独有的修饰符为ACC_STATIC(static)
     *                ACC_SYNCHRONIZED(同步的),ACC_VARARGS(不定参数个数的方法),ACC_NATIVE(native类型的方法)
     * @param name:方法名
     * @param desc:方法签名。格式为:(参数列表)返回值类型。比如方法为void setText(String s),它的desc属性值为:(Ljava/lang/String;)V
     *              参数类型(数组为[..;)(long型为J)(boolean型为Z)(对象型为L)
     * @param signature:表示与泛型相关的信息
     * @param exceptions:表示方法会抛出的异常,如果不会抛出异常则参数为空
     * @return
     */
    @Override
    MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        return super.visitMethod(access, name, desc, signature, exceptions)
    }
    /**
     * 遍历类中成员信息结束
     */
    @Override
    void visitEnd() {
        super.visitEnd()
    }
}

关于methodVisitor的说明:每次调用该方法时需要返回一个新的MethodVisitor实例,不应该返回一个以前用过的Visitor也就是说在这个方法中我们要重新new一个,比如下面

@Override
MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
    MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions)

    String nameDesc = name + desc

    methodVisitor = new SensorsAnalyticsDefaultMethodVisitor(methodVisitor, access, name, desc) {
        boolean isSensorsDataTrackViewOnClickAnnotation = false

        @Override
        protected void onMethodExit(int opcode) {
            super.onMethodExit(opcode)
            if (isSensorsDataTrackViewOnClickAnnotation) {
                methodVisitor.visitLdcInsn(visitor.typeName)
                methodVisitor.visitMethodInsn(INVOKESTATIC, SDK_API_CLASS, "trackViewOnClick", "(Ljava/lang/String;)V", false)
            }
        }

        @Override
        AnnotationVisitor visitAnnotation(String s, boolean b) {
            if (s == 'Lplugin/custom/mwy/com/sdk/SensorsDataTrackViewOnClick;') {
                isSensorsDataTrackViewOnClickAnnotation = true
            }
            visitor = new MyAnnotationVisitor(super.visitAnnotation(s, b))
            return visitor
        }
    }
    return methodVisitor
}

methodVisitor常用的方法有:onMethodEnter(方法开始的时候调用)、onMethodExit(方法结束时调用)、visitAnnotation(方法有注解时调用)。比如需要统计方法的耗时情况我们直接在onMethodEnter和onMethodExit中记录时间即可。如果要想埋点的话,一般在onMethodExit中进行打点,同时也可以配合注解来进行(比如上面就定义了一个注解)