Spring 解决循环依赖核心依靠:三级缓存 + 提前暴露早期半成品 Bean ,仅支持单例、构造器注入以外的场景。
一、什么是循环依赖
两个或多个 Bean 互相依赖:
java
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
二、核心原理:三级缓存
Spring 容器维护三个 Map(三级缓存):
-
singletonObjects(一级缓存)
- 存放完全初始化好的单例 Bean
-
earlySingletonObjects(二级缓存)
- 存放实例化完成,但未填充属性、未初始化的早期 Bean
-
singletonFactories(三级缓存)
- 存放创建早期 Bean 的ObjectFactory,用于获取早期引用
三、解决流程(A ↔ B)
- 创建 A:实例化 A,将 A 的 ObjectFactory 放入三级缓存,暴露早期引用
- 填充 A 属性:发现依赖 B,去创建 B
- 创建 B:实例化 B,将 B 的 ObjectFactory 放入三级缓存
- 填充 B 属性:发现依赖 A,从缓存获取 A:
- 先查一级缓存(无)
- 查二级缓存(无)
- 查三级缓存,拿到 A 的 ObjectFactory,获取早期 A 对象,放入二级缓存,并删除三级缓存
- B 完成属性填充 + 初始化,加入一级缓存
- 回到 A,拿到完整 B,完成 A 初始化,加入一级缓存
四、哪些情况无法解决
Spring 无法解决以下循环依赖,启动报错:
-
构造器注入循环依赖
java@Component public class A { // 构造器注入 B public A(B b) {} } @Component public class B { // 构造器注入 A public B(A a) {} }实例化时就互相依赖,无法提前暴露。
-
多例(prototype)Bean
多例不进入缓存,每次创建新实例,无法缓存提前引用。
-
使用
@DependsOn显式构成循环依赖。
五、无法解决时的方案
-
改用 setter / 字段注入(而非构造器)
-
使用
@Lazy懒加载:java@Component public class A { @Autowired @Lazy private B b; } -
拆分子功能,解除循环依赖
-
使用
ApplicationContextAware、InitializingBean手动获取依赖
六、总结
- 默认支持:单例 + setter / 字段注入循环依赖
- 核心手段:三级缓存提前暴露半成品 Bean
- 不支持 :构造器注入、多例、
@DependsOn循环 - 优化 :优先用
@Lazy或重构代码解除循环