动态代理(CGLIB/JDK)
动态代理(Dynamic Proxy)
CGLIB 代理和 JDK 动态代理是 Java 中实现动态代理的两种主流方式。
Spring AOP 底层就依赖它们来生成代理对象,从而在不修改原始代码的情况下增强方法行为(如日志、事务、权限校验等)。
JDK 动态代理(JDK Dynamic Proxy)
原理
- 基于 Java 反射机制。
- 只能代理实现了接口的类。
- 在运行时动态生成一个 实现了相同接口的代理类(如
$Proxy0),该代理类持有目标对象的引用,并在调用方法前后插入增强逻辑。
示例
1 | public interface UserService { |
JDK 代理会生成一个类似这样的代理类(伪代码):
1 | public final class $Proxy0 implements UserService { |
特点
| 优点 | 缺点 |
|---|---|
| 纯 JDK 内置,无需额外依赖 | 只能代理有接口的类 |
| 性能较好(尤其在新版本 JVM 中) | 无法代理类中未通过接口暴露的方法 |
| 安全、稳定 |
CGLIB 代理(Code Generation Library)
原理
- 基于 字节码生成技术(运行时动态生成子类)。
- 不需要目标类实现接口。
- 通过继承目标类,创建一个子类(如
UserServiceImpl$$EnhancerByCGLIB$$xxx),重写其非final方法,在方法中插入增强逻辑。
示例
1 | public class UserService { |
CGLIB 会生成一个子类:
1 | public class UserService$$EnhancerByCGLIB extends UserService { |
特点
| 优点 | 缺点 |
|---|---|
| 可以代理 没有接口的类 | 不能代理 final 类或 final/private 方法 |
| 更灵活,适用范围更广 | 启动稍慢(需生成字节码) |
| Spring 默认在无接口时自动使用 | 需要额外依赖(但 Spring Boot 已内置) |
💡 CGLIB 底层使用 ASM 字节码操作库,性能高效。
对比总结
| 特性 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 是否需要接口 | ✅ 必须实现接口 | ❌ 不需要 |
| 代理方式 | 实现接口 | 继承目标类 |
| 能否代理 final 类 | ❌(接口不能 final) | ❌(无法继承 final 类) |
| 能否代理 final 方法 | ✅(只要在接口中) | ❌(无法重写 final 方法) |
| 性能(JDK 17+) | ⚡ 很快 | ⚡ 接近 JDK 代理 |
| 依赖 | JDK 自带 | 需 CGLIB(Spring Boot 已包含) |
| Spring 默认策略 | 有接口 → JDK 无接口 → CGLIB |
可通过 proxyTargetClass=true 强制使用 |
Spring 中如何选择?
Spring AOP 默认策略:
1 | if (目标类 implements 接口) { |
但你可以强制统一使用 CGLIB(推荐,避免因接口问题导致代理失效):
1 |
实际建议
- 现代 Spring Boot 项目:直接开启
proxyTargetClass = true,统一使用 CGLIB,避免“部分 Bean 无法被代理”的坑。 - 不要将 Service 类设计为 final。
- 构造函数中不要调用自身其他方法(CGLIB 代理可能未生效)。
一句话总结:
JDK 代理靠接口,CGLIB 代理靠继承;Spring 默认智能选,强制 CGLIB 更省心。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 微光zc的网络小窝!
评论








