Class Transformation

ClassReader

ClassWriter用于生成字节码文件,而ClassReader用于读取字节码文件

配合字节码文件的结构去理解

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
  • classFileBuffer:读取到的字节码数据

  • cpInfoOffsets:字节码中的常量池(constant pool)的位置

  • header:字节码的访问标识符的位置

构造器读取字节码后,会根据class文件的结构填充这几个字段。

ClassReader读取如下类的基本信息:

access: 33

className: sample/HelloWorld

superName: java/lang/Exception

interfaces: [java/io/Serializable, java/lang/Cloneable]

ClassReader提供一个accept方法来让ClassVisitor访问字节码文件

第二个参数parsingOptions 可选值有以下5个,会对ClassVisitor的visit行为造成不同的影响

  • 0:生成所有ASM代码

  • ClassReader.SKIP_CODE:忽略代码信息,如visitXxxInsn调用

  • ClassReader.SKIP_DEBUG:忽略调试信息,如visitParametervisitLineNumbervisitLocalVariable

  • ClassReader.SKIP_FRAMES:忽略frame信息,如visitFrame

  • ClassReader.EXPAND_FRAMES:对frame信息进行扩展

使用ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES能得到功能完整,复杂度低的字节码文件

修改字节码文件的流程如下:

image-20230918094202847

ClassReader是旧字节码的入口,ClassWriter是新字节码的出口,中间可以有多个ClassVisitor来修改字节码。代码结构如下:

ClassVisitor是抽象类,将上面的cv替换为我们自定义的ClassVisitor子类即可

Best Practice

Modify Class Meta Data

重写visit方法

修改了visitinterfaces参数,同样其他参数也可以修改。如修改Java版本version,类名name,父类superName

Modify Class Field

重写visitField方法

Remove Field

正常情况下ClassVisitor#visitField会返回一个FieldVisitor对象,最后会调用其fv.visitEnd,返回null就断掉了

Add Field

同样,想要添加新字段,只需再调用一次ClassVisitor#visitField,再调用fv.visitEnd

但需要思考一下在哪里进行字段插入的操作。

一个类有几个字段就会执行几次visitField,若要在这里插入还需设置一个全局标志位来判断新字段是否插入,否则后面的visitField会造成重复插入。

visitEnd最后调用一次,是个不错的选择,就在这插入新字段。

Modify Class Method

重写MethodDelVisitor方法

Remove Method

和上面删除字段的思路一样。

Add Method

Update Method

enter & exit

如何在方法进入和退出时添加一些逻辑呢?

回想MethodVisitor的调用顺序

visitCode(方法体开始) -> visitXxxIns(方法体) -> visitMaxs -> visitEnd

方法进入时的逻辑可以在visitCode处添加,此时尚未进入方法体。但注意调用visitMaxs时已经退出方法体了,可能执行了return或throw异常,两种情况都是通过visitInsn(opcode)实现的,所以方法退出的逻辑可以在visitInsn处添加。

visitMethod中判断当前方法名和方法描述符是否为目标方法,是则返回自定义的MethodVisitorvisitInsn判断当前opcode是否为throw或return

AdviceAdapter

ASM提供了一个抽象类来实现在方法进入前后添加逻辑,它有两个方法onMethodEnteronMethodExit

The End

暂时到这吧,后面有需要再回过来补充。

Last updated

Was this helpful?