基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

什么情况下 Spring 会强制从 JDK 动态代理降级/切换为 CGLIB 代理?

知识点图片

在 Spring 框架中,动态代理机制主要有两种:JDK 动态代理(基于接口)和 CGLIB 代理(基于子类)。

虽然早期的 Spring 默认遵循“有接口就用 JDK,没接口才用 CGLIB”的原则,但在某些特定情况或配置下,Spring 会强制降级/切换为 CGLIB 代理。以下是触发这种切换的核心场景:

1. 目标类没有实现任何“业务接口” (物理强制)

这是最根本的原因。JDK 动态代理的底层机制要求目标类必须实现至少一个接口

  • 完全无接口:如果一个类纯粹是一个普通类(POJO),没有实现任何接口,Spring AOP 在为其创建代理时,别无选择,只能通过生成子类的方式,即强制使用 CGLIB。
  • 只有 Spring 内部标记接口:如果目标类仅仅实现了 Spring 的回调接口(如 InitializingBean, DisposableBean, ApplicationContextAware 等)或 JDK 的标记接口(如 Serializable, Cloneable),Spring 的代理机制(ProxyProcessorSupport类)会忽略这些非用户自定义的接口。此时,Spring 依然会认为该类没有实现“有效的业务接口”,从而强制切换为 CGLIB

2. 代理 @Configuration 注解的配置类 (框架强制)

当你在类上使用 @Configuration 注解时,Spring 会在启动时使用 CGLIB 对其进行增强。

  • 原因:在 @Configuration 类中,如果 @Bean 方法 A 调用了 @Bean 方法 B,为了保证方法 B 返回的始终是 Spring 容器中的同一个单例对象(而不是重新 new 一个新对象),Spring 必须拦截对方法 B 的调用。
  • 机制:JDK 动态代理无法拦截类内部的自我调用(self-invocation),也无法对未实现接口的配置类进行代理。因此,Spring 强制使用 CGLIB 生成配置类的子类,重写这些方法以植入从 IoC 容器中获取 Bean 的逻辑。

3. 按类型注入时使用了“实现类”而非“接口” (为避免异常而强制)

在实际开发中,这是一个非常容易踩坑的场景。
假设你有 public class UserServiceImpl implements UserService
如果你在其他地方这样注入:

java
@Autowired
private UserServiceImpl userService; // 注入的是实现类类型,而不是 UserService 接口
  • 如果使用 JDK 代理:Spring 生成的代理类实现了 UserService 接口,但它不是 UserServiceImpl 的子类。这会导致强转失败,抛出 BeanNotOfRequiredTypeException
  • 解决方案:为了防止这种依赖注入时的类型转换异常,开发者通常会通过配置强制 Spring 全部使用 CGLIB 代理

4. 显式开启了 proxyTargetClass = true (配置强制)

开发者或上层框架明确告诉 Spring:“不要管有没有接口,统统给我基于类生成代理(即 CGLIB)”。
常见的触发方式包括:

  • <aop:config proxy-target-class="true"> (XML配置)
  • @EnableAspectJAutoProxy(proxyTargetClass = true) (AOP 注解配置)
  • @EnableTransactionManagement(proxyTargetClass = true) (事务注解配置)

5. Spring Boot 2.0+ 的默认行为 (时代变迁导致的强制)

如果你使用的是 Spring Boot 2.0 及以上版本,你会发现哪怕你的类实现了接口,Spring 默认生成的依然是 CGLIB 代理。

  • 原因:Spring Boot 团队为了彻底解决上述第 3 点提到的 ClassCastException(开发者经常因为注入实现类而不是接口而报错),在 Spring Boot 2.0 中修改了默认配置。
  • 源码体现:自动配置类 AopAutoConfiguration 默认将 spring.aop.proxy-target-class 设为了 true。这意味着在现代 Spring Boot 应用中,默认就已经强制全局使用 CGLIB 了(除非你在 application.yml 中显式设置 spring.aop.proxy-target-class=false 才会退回到 JDK 代理)。

6. AOP 切点(Pointcut)命中了实现类特有的方法

假设 UserServiceImpl 实现 UserService 接口,但 UserServiceImpl 中有一个接口中不存在的方法 customMethod()
如果你的 AOP 切面表达式恰好切入了这个 customMethod() 方法,或者事务注解 @Transactional 仅加在了这个特有方法上。
由于 JDK 代理只能代理接口中定义的方法,为了让增强逻辑生效,Spring 在某些情况下(结合具体配置)会评估并使用 CGLIB 来代理目标类,因为只有 CGLIB 才能拦截实现类自有的方法。


总结

Spring 会在以下情况切换或强制使用 CGLIB:

  1. 不能用 JDK 时:目标类无接口,或只有 Spring/JDK 内部标记接口。
  2. 必须用 CGLIB 时:处理 @Configuration 配置类的单例拦截。
  3. 你要求它用时:显式配置了 proxyTargetClass=true
  4. 框架替你决定时:Spring Boot 2.0+ 默认开启全局 CGLIB 以防类型转换异常。
00:00
00:00