深入了解CGLIB底层字节码实现原理

时间:2024-04-11 09:10:00

目前字节码插装方法主要分为两类: 静态插装和动态插装。
​ 静态字节码插装是指被插装的程序在开始执行之前就插入了所有插装代码的插装方法; 该方法的主要优势是它消耗较少的程序额外运行时间, 因为所有的类都在程序执行前就已经插装了; 静态字节码插装还有一个优势, 就是它可以使用任何已有的上层字节码工程库, 比如 BCEL, ASM, JOIE, Javassist 等等。而静态字节码插装的最大的缺点是它无法对动态生成或动态加载的代码进行插装。

​ 而动态字节码插装是在待插装程序运行的同时执行插装; 每当有一个类被加载时, 插装代理程序都会被调用,并可能在被加载的字节码中插入装代码;动态字节码插装的缺点是它会带来大量额外时间消耗,但是另一方面,它保证了所有被加载的类都会被插装。

​ CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。

​ 类 TransformerLoader 是进行字节码插装的执行者和调度者。首先创建 ClassReader 实例,并加载指定的字节码,然后获取应用层传入的不同的 ClassAdapter实例,调用 ClassReader的accept 方法,使得相应的ClassAdapter实例遍历它所加载的字节码。

​ ClassReader 会在遍历字节码的不同位置时调度 ClassAdapter 不同的 visit 方法, 来根据不同的逻辑修改字节码。 根据 ASM 规范, 任何 ClassVistor 的实现者,如 ClassAdapter, 都需要按照如下的调用规则来遍历字节码: visit [ visitSource ][ visitOuterClass ] ( visitAnnotation | visitAttribute )* (visitInnerClass | visitField |visitMethod )* visitEnd。

​ 所以,ClassReader 会首先调用 visit 方法,遍历类的头部信息, 包括版本号,类签名等。然后对类的每一个字段都调用 visitField 方法,按照ClassAdapter的逻辑对字节码的字段进行相应的修改。

​ 在完成对字节码字段的修改之后,ClassReader将会调用visitMethod方法,按照 ClassAdapter 中的重载实现来进行修改,这里是进行字节码插装的关键,可以根据需要加入我们所需要的探针, 以及任何我们需要的程序逻辑, 如计时器等。

​ 而所有 MethodVisitor 的实现者, 如 MethodAdapter 等, 都必须按如下的调用顺寻来遍历类方法所对应的字节码指令: [visitAnnotationDefault](visitAnnotation | visitParameterAnnotation | visitAttribute) * [visitCode (visitFrame| visitXInsn | visitLabel | visitTryCatchBlock | visitLocalVariable |visitLineNumber)* visitMaxs] visitEnd.

​ 最后,必须要调用visitEnd方法,来通知ClassVisitor,该字节码的所有字段和方法都已经被遍历过了。ClassAdapter在visitEnd方法结束前, 任然可以对字节码进行最后的修改, 比如,可以在次定义探针变量等。

深入了解CGLIB底层字节码实现原理

​ 如图所示的系统时序图的交互过程中, 客户端应用程序先创建一个ClassReader 对象实例, 然后以 ClassAdapter 实例作为参数调用该对象实例的accept()方法, 完成这个调用之后, 传入的 ClassAdapter 就可以在需要的时候遍历该 ClassReader 实例所读取的字节码。

​ ClassReader 类对象的实例对类以及类的每一个字节码进行解析并发送 visit消息给刚才传入的 ClassAdapter 对象。 对不同的上下文环境, 如属性(Fields)、方法(Methods)、 注解(Annotation), ClassVisitor 对象可以分别创建相应的访问者,如继承自 接口 FieldVisitor, MethodVisitor 或者 AnnotationVisitor 的访问者,并在遍历到不同的字节码时调用相应访问者对象的访问方法, 如在访问到方法字节码时,调用 MethodVisitor 对象的 visitMethod 等。 如果生产者, 即 ClassReader对象接受到的字节码指令非空,那么相应的子上下文事件就传递给对应的访问者类的实例对象。最后子上下文结束时,生产者 ClassReader 对象将会调用visitEnd 方法通知不同的访问者, 然后继续访问下一个子上下文环境, 如此继续,直到整个类整个字节码被访问完毕。

​ 最后 ClassWriter 访问者对象处理字节码访问的最后一步, 它同时也是最终被修改完成的字节码的生成者, 它可以将最终字节码产生成一个 class 文件或者直接返回字节码的 byte 数组, 供应用层使用。