Spring AOP — JDK动态代理 vs CGLIB
Spring AOP 的底层实现基于动态代理。Spring 会根据目标对象是否实现了接口,自动选择 JDK 动态代理或 CGLIB 代理。理解两者的区别和 Spring 的选择逻辑,是深入 Spring AOP 的基础。
一、JDK 动态代理
JDK 动态代理是 Java 原生的代理机制,基于接口实现。
1.1 工作原理
1 | 客户端 → [Proxy (implements Interface)] → [InvocationHandler] → [真实对象] |
核心 API:
1 | // 创建代理对象 |
1.2 特点
| 特点 | 说明 |
|---|---|
| 必须实现接口 | 只能代理实现了至少一个接口的类 |
| 性能 | 反射调用,略慢于 CGLIB |
| JDK 内置 | 无需第三方依赖 |
| 代理对象类型 | $Proxy0 extends Proxy implements MyService |
1.3 底层原理
1 | // ProxyGenerator 生成的字节码(简化版) |
Proxy.newProxyInstance() 在运行时动态生成一个实现了目标接口的代理类,该类持有 InvocationHandler 引用,所有方法调用都转发到 InvocationHandler.invoke()。
二、CGLIB 代理
CGLIB(Code Generation Library)是基于字节码生成的代理方式,可以代理没有实现接口的类。
2.1 工作原理
1 | 客户端 → [Proxy (extends Target)] → [MethodInterceptor] → [真实对象] |
核心 API:
1 | Enhancer enhancer = new Enhancer(); |
2.2 特点
| 特点 | 说明 |
|---|---|
| 不需要接口 | 可以代理任何非 final 类 |
| final 方法限制 | 不能代理 final 方法(JVM 不允许重写) |
| 性能 | 使用 FastClass 机制,比 JDK 反射快 |
| 额外依赖 | 需要 cglib 和 asm 库 |
| 代理对象类型 | TargetClass$$EnhancerByCGLIB$$xxxx |
2.3 底层原理
CGLIB 使用 ASM 字节码框架,在运行时生成目标类的子类:
1 | // CGLIB 生成的字节码(简化版) |
CGLIB 的 FastClass 机制避免了反射:通过为每个方法分配一个 index,调用时通过 switch-case 分发,比反射快 3-5 倍。
三、Spring 代理选择策略
3.1 Spring Boot 2.x 之后的默认行为
| 场景 | Spring 使用的代理 |
|---|---|
| 目标类实现了接口 | JDK 动态代理 |
| 目标类未实现接口 | CGLIB |
proxyTargetClass = true |
强制使用 CGLIB |
| Spring Boot 2.0+ 默认 | CGLIB(通过 spring.aop.proxy-target-class=true) |
在 Spring Boot 2.0 之前,默认使用 JDK 动态代理;2.0 之后改为 CGLIB。
3.2 切换代理方式
1 | # application.yml |
3.3 AOP 失效的常见场景
场景 1:内部方法调用(self-invocation)
1 |
|
原因:this.methodB() 是直接调用,绕过了代理对象。解决方法:注入自身、使用 AopContext.currentProxy()、或将 methodB 移到另一个 Service。
场景 2:非 public 方法
1 |
|
JDK 动态代理要求方法在接口中(天然 public),CGLIB 也无法代理 private 方法(字节码层面的限制)。
场景 3:final 类或 final 方法
1 |
|
CGLIB 通过生成子类实现代理,final 类无法被继承。
四、性能对比
一般场景下两者差异很小,以下是约百万次调用的对比:
| 指标 | JDK 动态代理 | CGLIB |
|---|---|---|
| 创建代理(首次) | 快 | 慢(生成字节码) |
| 方法调用(后续) | 略慢(反射) | 快(FastClass 索引) |
| 内存占用 | 小 | 较大(代理类对象) |
在 Spring 的实际使用中,这个性能差异微乎其微——AOP 主要用于事务管理、日志记录等横切关注点,这些操作本身的耗时远大于代理调用的开销。
五、小结
Spring AOP 的两种代理模式各有优势:JDK 动态代理基于接口,无需第三方依赖,实现更纯净;CGLIB 基于继承,更灵活但有一些限制(final 类/方法)。Spring Boot 2.0 后默认使用 CGLIB,因为实际开发中大部分 Service 类并不专门定义接口。理解 AOP 的代理机制不仅能帮你写出正确的代码,也能在遇到”事务失效”、”切面不生效”等问题时快速定位根因。