Spring通过三级缓存解决单例Bean的循环依赖(属性注入),但构造器注入和原型Bean无法处理,需避免或使用@Lazy延迟加载。
Spring循环依赖详解
1. 循环依赖的产生场景
当两个或多个Bean相互直接或间接依赖,形成闭环时,触发循环依赖。例如:
- Bean A → Bean B → Bean A
- Bean A → Bean B → Bean C → Bean A
2. Spring的解决机制(单例模式)
通过 三级缓存 提前暴露未初始化的Bean引用:
- singletonObjects:缓存完全初始化的Bean。
- earlySingletonObjects:缓存提前暴露的原始Bean(未完成属性注入)。
- singletonFactories:缓存Bean的ObjectFactory(用于生成早期代理对象)。
解决流程示例(A → B → A) :
- 实例化A(调用构造函数),将A的ObjectFactory放入三级缓存。
- 填充A的属性时发现依赖B,触发B的实例化。
- 实例化B,填充属性时发现依赖A,从三级缓存获取A的早期引用并注入。
- B完成初始化后放入一级缓存,A继续完成属性注入和初始化。
3. 无法解决的场景
- 构造器注入循环依赖 : 构造函数需在实例化时完成所有依赖注入,无法提前暴露引用,抛出
BeanCurrentlyInCreationException
。 - 原型(Prototype)Bean的循环依赖: Spring不缓存原型Bean,每次请求生成新实例,无法通过三级缓存解决。
4. 解决方案
-
代码重构:引入中间层或接口解耦。
-
使用Setter/字段注入:替代构造器注入。
-
@Lazy延迟加载:对依赖生成代理,延迟初始化。
less@Component public class ServiceA { @Lazy @Autowired private ServiceB serviceB; }
-
调整Bean初始化顺序 :通过
@DependsOn
强制指定顺序。
5. 源码关键逻辑
- 提前暴露引用 :
AbstractAutowireCapableBeanFactory.doCreateBean()
中调用addSingletonFactory
。 - 依赖解析 :
DefaultSingletonBeanRegistry.getSingleton()
从缓存中查找Bean。
6. 最佳实践
- 优先使用Setter注入:避免构造器循环依赖。
- 避免过度依赖:通过模块化设计减少直接依赖。
- 监控与日志 :启用
-Dspring.main.allow-circular-references=true
(Spring Boot 2.6+默认禁用循环依赖)。
总结:Spring通过三级缓存解决单例属性注入的循环依赖,但需避免构造器注入和原型作用域的循环场景,合理使用@Lazy和设计模式降低耦合。