spring aop默认使用jdkdynamicaopproxy,是因为只要目标类实现至少一个接口且proxytargetclass=false,defaultaopproxyfactory就优先选它——此举强制面向接口编程,保障解耦;仅当目标类无接口、proxytargetclass=true且非final时才退至objenesiscglibaopproxy。

Spring AOP 默认走 JdkDynamicAopProxy 的真实原因
不是因为“JDK 更轻量”或“CGLIB 太重”,而是 Spring 在设计时把「接口契约」当作解耦的基础设施来用。只要目标类实现了至少一个接口,DefaultAopProxyFactory 就会优先选择 JdkDynamicAopProxy —— 这个决策在 ProxyFactory.isProxyTargetClass() 为 false 且目标类有接口时被触发。
关键点在于:JDK 动态代理生成的是接口类型对象,调用方只依赖接口,不感知实现类;而 CGLIB 生成的是子类,调用方直接持有具体类型,天然绑定实现细节。
- 接口代理强制你面向抽象编程,比如
UserService接口被代理后,注入点写@Autowired UserService即可,无需关心背后是UserServiceImpl还是CachingUserServiceImpl - 一旦用了 CGLIB(即
ObjenesisCglibAopProxy),代理对象类型是UserServiceImpl$$EnhancerBySpringCGLIB$$xxx,若某处硬编码了instanceof UserServiceImpl或反射取类名,就可能出问题 -
@EnableAspectJAutoProxy(proxyTargetClass = false)是默认值,明确表达了 Spring 对接口代理的倾向
什么时候会退到 CGLIB?看这三个条件
Spring 不是“偏好 JDK”,而是“能用接口就绝不用继承”。退到 CGLIB 只发生在同时满足以下三点:
- 目标类没有实现任何接口(哪怕只有
Serializable都算“有接口”,会继续走 JDK) -
proxyTargetClass = true(显式开启),或spring.aop.proxy-target-class=true(配置开启) - 该类不是
final(CGLIB 需要能继承它)
常见误判场景:@Service 类没写 implements 任何接口,但父类实现了接口——这不算!CGLIB 判定只看当前类的 getInterfaces() 是否为空数组,不查继承链。
接口代理如何支撑解耦架构
这不是语法糖,而是影响组件生命周期的设计约束。接口代理让“增强逻辑”和“被增强对象”之间只通过契约通信,从而实现空间解耦:
- 切面(
@Aspect)可以独立编译、热替换,只要接口不变,不影响业务 Bean 的加载顺序或初始化时机 - 多个模块通过同一接口协作时,AOP 增强可以只加在消费端(如 Feign Client),或只加在提供端(如 Controller),互不感知
- 测试时可直接 new 实现类绕过代理,而 CGLIB 代理对象无法用
new构造,mock 成本更高
反例:用 @Transactional 修饰一个无接口的 @Service 类,又在另一个类里用 ApplicationContext.getBean(MyService.class) 强转获取——此时 CGLIB 代理暴露了具体类型,等于把横切逻辑泄漏进业务调用链。
想强制统一用 CGLIB?先确认你真需要它
除非你明确遇到 JDK 代理的限制,否则别轻易开 proxyTargetClass = true。典型刚需场景只有两个:
- 要对 final 类做 AOP(但 Spring 官方不建议 final 类参与 AOP,应重构)
- 必须拦截
private或final方法(JDK 代理完全做不到,CGLIB 也仅支持非 final 实例方法)
其他所谓“理由”往往站不住脚:CGLIB 性能并不比 JDK 显著差(现代 JVM 对子类生成优化很好),启动慢的问题在 Spring Boot 2.6+ 已缓解,而它带来的类型污染、序列化异常(NotSerializableException)、Mock 困难等问题却真实存在。
最常被忽略的一点:CGLIB 代理对象无法被 JDK 动态代理再次代理——如果某个 Bean 同时被多个 AOP 切面匹配,又混用了 JDK 和 CGLIB 策略,Spring 会静默降级为单一代理机制,部分切面可能失效。











