jdk动态代理只能代理接口,无法代理无接口的pojo类;cglib通过字节码生成子类实现代理,支持无接口场景;spring boot 2起默认启用cglib代理以提升兼容性与性能。

因为JDK动态代理根本做不到——它只能代理接口,而CGLIB能直接“复制并改造”类本身。
JDK代理有硬性门槛:必须有接口
JDK动态代理依赖反射和接口多态,运行时生成一个新类,这个类必须实现目标类所声明的某个接口。如果目标类是纯POJO(比如UserService没实现任何接口),JDK就无法构造代理对象,连创建步骤都会失败。
- 接口是JDK代理的“入场券”,缺一不可
- 哪怕只少一个接口,整个AOP织入流程就会中断
- 事务、日志、权限等切面逻辑也就无法生效
CGLIB用字节码“生个子类”来绕过限制
CGLIB不靠接口,而是用ASM在内存中动态生成目标类的子类(例如UserService$$EnhancerByCGLIB$$xxx),重写所有非final方法,在方法前后插入拦截逻辑。这相当于给原类“现场加了个儿子”,天然支持无接口场景。
- 代理对象和原对象是继承关系,不是接口实现关系
- 只要类和方法不是final,就能被增强
- Spring AOP因此能覆盖更广的业务代码,包括第三方jar里的类
Spring默认策略其实是“能用CGLIB就用”
从Spring Boot 2开始,框架已默认统一启用CGLIB代理(proxyTargetClass = true),不再按“有无接口”来回切换。这不是偏好,而是工程权衡:
- 避免开发者为凑接口而写冗余代码
- 减少运行时判断开销(不用每次检查是否实现接口)
- 方法调用走的是直接调用+invokeSuper,比JDK的反射调用更快
当然也有代价,但Spring已做了优化
CGLIB生成代理类的过程比JDK略重(涉及字节码操作),不过Spring通过Objenesis跳过构造器调用、缓存Enhancer实例、复用FastClass索引等方式大幅降低了初始化成本。日常使用中,这点延迟几乎不可感知,而换来的是更少约束、更稳兼容、更广适用。











