ASM 是一个 Java 字节码操作框架,它允许你在不破坏原有逻辑的情况下,动态地生成、修改和转换字节码。CGLIB 等著名的动态代理框架底层正是基于 ASM 来实现的。
要在方法执行前后插入自定义逻辑(类似 AOP 面向切面编程),核心是理解 ASM 的访问者模式(Visitor Pattern)。以下是 ASM 的基本方法和使用步骤:
1. ASM 的核心组件
在操作字节码时,主要会用到以下三个核心类:
- ClassReader(类读取器):负责读取已经编译好的
.class文件的字节码,并将解析出的类结构以事件的形式传递给ClassVisitor。 - ClassVisitor(类访问者):抽象类,用于访问和修改类的结构。当
ClassReader解析到类的方法、字段等节点时,会回调ClassVisitor中对应的visit方法。 - ClassWriter(类写入器):
ClassVisitor的一个实现类。它负责接收修改后的字节码事件,并最终通过toByteArray()方法生成修改后的字节码数组。
2. 实现方法执行前后动作的核心逻辑
要在方法执行前后插入逻辑,需要结合ClassVisitor和MethodVisitor(方法访问者)来实现。核心步骤如下:
第一步:拦截目标方法
重写ClassVisitor的visitMethod方法。在这个方法中,你可以判断当前正在访问的方法名,如果匹配目标方法,则返回一个自定义的MethodVisitor;否则返回默认的MethodVisitor。
第二步:在方法进入时插入前置逻辑
在自定义的MethodVisitor中,重写visitCode()方法。该方法会在 ASM 开始访问某一个方法的 Code 区(即方法体)时被调用。在这里插入的字节码指令,就会在目标方法执行前运行。
第三步:在方法退出时插入后置逻辑
重写visitInsn(int opcode)方法。ASM 在访问到无参数的指令(如return或athrow)时会回调此方法。你需要判断当前的opcode是否为返回或抛出异常的指令,如果是,则在执行该指令前插入后置逻辑的字节码。
3. 代码实战演示
假设我们有一个Base类,其中包含一个process()方法。我们希望在process()方法执行前打印 "start",执行后打印 "end"。
java
import org.objectweb.asm.*; public class AopClassVisitor extends ClassVisitor { public AopClassVisitor(ClassVisitor classVisitor) { super(Opcodes.ASM9, classVisitor); } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); // 1. 拦截目标方法,跳过构造方法 <init> if (mv != null && !"process".equals(name) && !"<init>".equals(name)) { return mv; } if ("<init>".equals(name)) { return mv; } // 返回自定义的 MethodVisitor 来处理 process 方法 return new MethodVisitor(Opcodes.ASM9, mv) { // 2. 方法进入时,插入前置逻辑 @Override public void visitCode() { super.visitCode(); // 插入 System.out.println("start"); 的字节码 mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("start"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); } // 3. 方法退出时,插入后置逻辑 @Override public void visitInsn(int opcode) { // 判断是否是 return 或 throw 指令 if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) { // 在真正返回前,插入 System.out.println("end"); 的字节码 mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("end"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); } // 执行原有的 return 或 throw 指令 super.visitInsn(opcode); } }; } }4. 完整的转换流程
将上述ClassVisitor与ClassReader和ClassWriter结合,即可完成字节码的修改:
java
// 1. 读取原始类的字节码 ClassReader cr = new ClassReader("com/example/Base"); // 2. 创建 ClassWriter 用于生成新字节码 ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES); // 3. 使用自定义的 AopClassVisitor 进行处理 ClassVisitor cv = new AopClassVisitor(cw); // 4. 触发解析和修改过程 cr.accept(cv, 0); // 5. 获取修改后的字节码数组 byte[] newBytes = cw.toByteArray();下面我将一步步解析CGLIB是如何利用 ASM 实现这一过程的,并辅以带详细注释的代码示例。
第一步:定义目标类和拦截器
首先,我们需要一个被代理的普通类(无需实现接口)和一个用于定义增强逻辑的拦截器。
java
// 1. 目标类:一个普通的类,没有实现任何接口 public class UserService { public void addUser(String username) { System.out.println("【核心业务】添加用户: " + username); } } // 2. 拦截器:实现 MethodInterceptor 接口,定义前置和后置逻辑 import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class LogInterceptor implements MethodInterceptor { /** * 所有被代理的方法调用都会经过这里 * @param obj 代理对象本身 * @param method 被调用的方法 * @param args 方法参数 * @param methodProxy 用于调用父类(目标类)原始方法的代理 */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 前置增强逻辑 System.out.println("[前置日志] 方法 " + method.getName() + " 开始执行..."); // 调用目标类的原始方法 // invokeSuper 会绕过代理,直接调用父类(UserService)的方法 Object result = methodProxy.invokeSuper(obj, args); // 后置增强逻辑 System.out.println("[后置日志] 方法 " + method.getName() + " 执行完毕."); return result; } }第二步:使用 Enhancer 创建代理对象
Enhancer是 CGLIB 的核心入口类。它负责配置代理参数并触发代理类的生成。
java
import net.sf.cglib.proxy.Enhancer; public class CglibDemo { public static void main(String[] args) { // 1. 创建 Enhancer 对象,它是代理类的“工厂” Enhancer enhancer = new Enhancer(); // 2. 设置父类(即目标类)。CGLIB 会通过继承来创建代理类 enhancer.setSuperclass(UserService.class); // 3. 设置回调函数。当代理类的方法被调用时,会执行这个拦截器 enhancer.setCallback(new LogInterceptor()); // 4. 创建代理对象。 // 【关键步骤】在这里,CGLIB 会调用底层的 ASM,在内存中动态生成一个 // UserService 的子类,例如 UserService$$EnhancerByCGLIB$$xxxx UserService proxy = (UserService) enhancer.create(); // 5. 调用代理对象的方法,触发拦截器逻辑 proxy.addUser("Alice"); } }运行结果:
text
[前置日志] 方法 addUser 开始执行... 【核心业务】添加用户: Alice [后置日志] 方法 addUser 执行完毕.第三步:CGLIB 底层如何使用 ASM(核心原理)
当你调用enhancer.create()时,CGLIB 内部会启动一个复杂的字节码生成流程。虽然我们无法看到 CGLIB 内部的源码,但其核心逻辑可以概括为以下使用 ASM 的步骤:
- 分析目标类:CGLIB 首先会读取
UserService的字节码,分析其所有非final的方法。 - 生成子类骨架:使用 ASM 的
ClassWriter开始构建一个新的类,这个类继承自UserService,并实现 CGLIB 内部的Factory接口。 - 重写方法并注入拦截逻辑:这是最关键的一步。CGLIB 会为每一个需要代理的方法(如
addUser)生成重写代码。用 ASM 的伪代码表示如下:
java
// 这是 CGLIB 通过 ASM 在内存中生成的代理类的大致结构 public class UserService$$EnhancerByCGLIB$$xxxx extends UserService { // CGLIB 会在类中生成一个字段,用来持有我们设置的拦截器 private MethodInterceptor CGLIB$CALLBACK_0; // 重写目标类的 addUser 方法 @Override public void addUser(String username) { // 【ASM 生成的字节码逻辑】 // 1. 获取拦截器实例 MethodInterceptor interceptor = this.CGLIB$CALLBACK_0; // 2. 如果拦截器不为空,则将调用委托给它 if (interceptor != null) { // 调用 intercept 方法,并将 this、Method对象、参数、MethodProxy 传入 // 注意:CGLIB 会预先缓存 Method 和 MethodProxy 对象以提升性能 interceptor.intercept( this, CGLIB$addUser$Method, // 预缓存的 Method 对象 new Object[]{username}, // 参数数组 CGLIB$addUser$Proxy // 预缓存的 MethodProxy 对象 ); } else { // 3. 如果没有拦截器,则直接调用父类方法 super.addUser(username); } } }- 生成 FastClass 以优化性能:为了避免 Java 反射的性能开销,CGLIB 还会利用 ASM 生成两个额外的
FastClass:- 一个为代理类生成方法索引,使得
MethodProxy.invokeSuper()可以通过索引直接调用父类方法,而不是通过反射。 - 一个为目标类生成方法索引,用于其他场景下的快速方法调用。
- 一个为代理类生成方法索引,使得
总结
CGLIB 对 ASM 的使用,本质上是将“在方法前后插入逻辑”这个高级需求,翻译成了底层的字节码操作:
- 它用 ASM 创建了一个子类(
ClassWriter,visitMethod)。 - 它用 ASM 重写了目标方法,并在方法体中插入了调用
MethodInterceptor.intercept()的指令(MethodVisitor,visitCode,visitMethodInsn)。 - 它用 ASM 生成了辅助类(
FastClass),通过方法索引来替代反射,极大地提升了运行时性能。