通俗易懂!Spring 动态代理 vs CGLIB 一次讲透(2025 面试必考,画图+口诀,背完直接秒杀)
一句话记住区别:
- JDK 动态代理:必须实现接口,像“替身演员”只模仿有剧本(接口)的角色
- CGLIB:直接给类生成子类,像“整容”一样连没接口的类也能代理(Spring 默认选它)
| 项目 | JDK 动态代理 | CGLIB(Code Generation Library) | 2025 年谁更常用? |
|---|---|---|---|
| 能不能代理的类 | 必须实现接口 | 接口、普通类、final 类都不行 | CGLIB 完胜 |
| 底层实现 | java.lang.reflect.Proxy | ASM 字节码操作(生成子类) | —— |
| 代理后对象类型 | 实现原接口 | 继承原类(是原类的子类) | —— |
| 能不能代理 private/final 方法 | 不能(只能 public) | 不能 final 方法,但能 private(实际很少用) | —— |
| 性能 | 稍快(反射调用) | 更快(直接子类方法调用) | CGLIB 更快 |
| Spring 默认用谁 | 有接口 → 用 JDK | 无接口 或 强制用 → 用 CGLIB | CGLIB |
| Spring Boot 3.x 默认 | 直接默认 CGLIB(连有接口也用 CGLIB) | —— | CGLIB 统治世界 |
Spring 到底什么时候用哪一个?(2025 真实规则)
| 情况 | Spring 用谁? | 说明 |
|---|---|---|
| 被代理的类实现了接口(传统写法) | 默认用 JDK 动态代理 | Spring 5 以前的默认行为 |
| 被代理的类没有实现任何接口 | 强制用 CGLIB | 必须用子类方式 |
| 加了 @Transactional 的类有接口 | Spring Boot 3 默认 CGLIB | 强制用 CGLIB(推荐) |
| 被代理的类是 final | 报错!两个都不能代理 | final 类无法继承,final 方法无法覆盖 |
| 方法是 final / static / private | AOP 不生效 | 两个代理都只能代理 public 非 final 方法 |
Spring Boot 3(Spring 6)已经彻底抛弃 JDK 代理了!
从 Spring Boot 3.0 开始:
# application.yml 一行配置就能全局强制用 CGLIB(默认就是 true)
spring:
aop:
proxy-target-class: true # 默认就是 true!强制 CGLIB
也就是说:2025 年你写 @Transactional、@Cacheable、自定义切面,99.99% 都是 CGLIB 在干活!
经典面试题 + 标准答案(直接背)
- Spring AOP 默认用 JDK 还是 CGLIB?
答:Spring Boot 3.x 默认强制 CGLIB(proxy-target-class=true),即使有接口也用 CGLIB。 - 为什么 Spring Boot 3 要强制用 CGLIB?
答:
- 避免“接口多了不知道用哪个”的歧义
- CGLIB 性能更好(直接子类调用 > 接口反射)
- 支持记录真实类名(日志里显示 UserService 而不是 UserService$$EnhancerBySpringCGLIB)
- final 类能被代理吗?
答:不能!CGLIB 需要生成子类,final 类禁止继承,直接报错。 - 同一个类里 this.method() 调用,AOP 会生效吗?
答:不会!因为没走代理。
解决办法:
- 用 @Autowired 把自己注入自己
- 用 AopContext.currentProxy()
- 或者直接别在内部调用,暴露一个 Service 方法给外部调
- 怎么看一个对象是不是代理对象?
答:
- 看类名:UserService$$EnhancerBySpringCGLIB$$ 开头就是 CGLIB
- 代码里打印:System.out.println(userService.getClass().getName());
- JDK 动态代理和 CGLIB 性能差距大吗?
答:现代 JVM 下差距很小,但 CGLIB 还是更快一点。Spring Boot 3 直接选 CGLIB 就是为了性能+一致性。
终极记忆口诀(10 秒背会)
“有接口曾经用 JDK,Boot 3 之后全 CGLIB
final 类来直接死,内部调用不生效
日志看类带 Enhancer,CGLIB 在背后使劲!”
面试画图神器(30 秒画完)
JDK 动态代理 CGLIB(2025 主流)
┌─────────────┐ ┌─────────────┐
│ Proxy$1 │◄─────接口──│ 子类 │
└─────────────┘ └─────────────┘
▲ ▲
└─────────实现───────────────┘
Target(原类)
背完这张表 + 口诀 + 6 个答案,
面试官问“Spring 动态代理和 CGLIB 区别”,你 1 分钟画图 + 甩代码 + 说 Boot 3 默认 CGLIB,
他直接在简历写“Spring AOP 原理极扎实”!
现在你已经彻底搞懂 Spring 底层代理了,大厂随便进!冲!