ASM

ASM是个2002年就开始的项目,真是古老至极。许多流行且包含了动态代理功能的框架应该直接或者间接使用了asm(cglib是基于asm做的二次开发)。

在最近的项目中使用到了asm,所以做了一些小记,内容有翻译,有摘录,有总结。

ASM 模型

ASM提供了两套API:

  • core API : 提供 event-based 字节码控制。

    asm通过JVMS定义的字节码结构把字节码中的如field,method 声明,指令等变成event,然后操纵event以达成修改字节码的目的。

    相关主要类:ClassVisitor, MethodVisitor (4.0之前还有基于这两个类的如 ClassAdapter 类等)

  • tree API : 提供 tree-based 字节码控制

    基于event-based的模型,继续抽象了下,用一棵树的形式展现字节码。

    相关主要类:ClassNode, MethodNode

此文中就拿了SAX 和 DOM 来描述解析xml的比较 来 类比 core API 和 tree API 的比较:

  • core API 比 tree API 占用更少的系统资源。从内存的角度看:tree API 由于要把字节码抽象成tree,在内存中会占用跟多的空间

  • core API 比 tree API 更难用,每次只能操作一个指令,需要非常了解字节码相关规范,写起来要小心翼翼。

ASM 架构

经典图如下:
经典图

使用了经典的 producer-consumer 模型,中间的白框都是filter。

producer --> filters --> consumer:

对于Class:

ClassReader 是 producer,filter 都是 ClassVisitor 的实现类,ClassWriter 作为 consumer。

对于Method:

ClassReader 是 producer, filter 是 MethodVistor 的实现类, MethodWriter 作为consumer。

对于Field:

ClassReader 是 producer, filter 是 FieldVistor 的实现类, FieldWriter 作为consumer。

ASM 实例

添加System.out.println(“here I am”); 代码到方法中:

  • core api使用方法如下:

    ClassReader cr = new ClassReader(bytes);
    ClassWriter cw = new ClassWriter(cr, COMPUTE_FRAMES | COMPUTE_MAXS); //cr只是定义来源reader,无用。
    ClassVisitor cv = new ClassVisitor(Opcodes.ASM5, cw){
        ....
    }
    cr.accept(cv, ClassReader.EXPAND_FRAMES);                
    return cw.toByteArray();
    
    //cr-->cv-->cw
    
  • tree api使用方法如下:

    ClassReader cr = new ClassReader(bytecode);
    ClassNode cn = new ClassNode();
    cr.accept(cn, ClassReader.SKIP_DEBUG);
    
    List methods = cn.methods;
    for (int i = 0; i < methods.size(); ++i) {
        MethodNode method = (MethodNode) methods.get(i);
        if (method.instructions.size() > 0) {                   instructions.insert(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); 
            instructions.insert(new LdcInsnNode("hi, here I am!"));
            instructions.insert(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false))
        }
    }
    ClassWriter cw = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS);
    cn.accept(c);
    return cw.toByteArray();
    
    //cr-->cn-->cw
    

PS:推荐在IDE上面安装ASM字节码工具,在一个正常类里面写完代码以后,用工具一转,可以参考转出的字节码,开始写asm相关代码了。

ASM其他

ASM core api

  • TraceClassVisitor:

    通过 PrintWriter 实例打印可读的字节码信息。

  • CheckClassAdapter

    在被JVM拒绝之前检测修改后的字节码是否合法。

  • AnalyzerAdapter

    可以自动计算stack的深度,感觉没太大作用,在ClassWriter的option里面有两个选项:COMPUTE_FRAMES, COMPUTE_MAXS

  • LocalVariablesSorter

    有newLocal方法可以添加local variable,自动维护local variable数量变化,引起的local variable对应的index变化的问题。

  • AdviceAdapter

    有onMethodEnter()和onMethodExit()方法,可以在方法最开始和RETURN 或者 ATHROW指令之前插入指令。

Metadata 相关 vistor:SignatureVisitor, AnnotationVisitor。这两个还没有用过,暂做记录。

ASM tree api

实际使用的时候,碰到了问题,没有细看,暂做记录。