Spring 仅能解决 "单例 Bean + setter 注入/字段注入" 场景的循环依赖,核心方案是通过 "三级缓存机制" 提前暴露"半成品 Bean",打破依赖循环,具体流程和原理如下:
一、核心前提:仅支持特定场景
Spring 无法解决所有循环依赖,仅针对满足以下条件的场景生效:
Bean 作用域:必须是 单例(多例 Bean 不存入缓存,无法提前暴露)。
注入方式:必须是 setter 注入 或 字段注入(构造器注入卡在实例化阶段,无法提前暴露半成品)。
二、核心方案:三级缓存的协作
Spring 通过三个缓存(Map 结构)分层存储 Bean 的不同状态,实现"提前暴露半成品 → 注入依赖 → 完成初始化"的闭环,打破循环。
- 三级缓存的职责
缓存级别 | 缓存名称(源码中) | 存储内容 | 核心作用 |
---|---|---|---|
第一级缓存 | singletonObjects | 完全初始化完成的单例 Bean | 供容器后续直接获取已就绪的 Bean,避免重复初始化,是最终的"成品缓存"。 |
第二级缓存 | earlySingletonObjects | 提前暴露的半成品单例 Bean(已实例化,但未执行 populateBean 注入属性、未执行 initializeBean 初始化方法) | 当循环依赖触发时,直接从该缓存获取"半成品 Bean",避免重复执行"获取半成品"的逻辑(如动态代理创建),提升效率。 |
第三级缓存 | singletonFactories | 创建半成品 Bean 的工厂对象(ObjectFactory 接口实现) | 1. 当单例 Bean 完成实例化(调用构造器)后,立即将其封装为 ObjectFactory 存入该缓存,实现"提前暴露"的第一步;2. 若 Bean 需要动态代理(如 @Transactional 标记的 Bean),工厂会在首次被调用时(循环依赖触发时)创建代理对象,确保后续注入的是代理实例,而非原始 Bean。 |
三、解决循环依赖的完整流程(示例:A 依赖 B,B 依赖 A)

流程图
以"单例 Bean A(setter 注入 B)"和"单例 Bean B(setter 注入 A)"为例,流程如下:
1.初始化 Bean A:
css
执行 A 的构造器,完成 实例化(此时 A 是半成品,无属性 B);
将 A 封装为 ObjectFactory,存入 3级缓存(singletonFactories),完成"提前暴露"的第一步;
开始为 A 注入属性 B,触发 Bean B 的初始化。
2.初始化 Bean B:
css
执行 B 的构造器,完成 实例化(此时 B 是半成品,无属性 A);
将 B 封装为 ObjectFactory,存入 3级缓存;
开始为 B 注入属性 A,触发 A 的获取。
3.获取半成品 A 注入 B:
css
先查 1级缓存:A 未完成初始化,无;
再查 2级缓存:A 未被暴露到这里,无;
最后查 3级缓存:找到 A 的 ObjectFactory,调用工厂方法生成 A 的半成品(若 A 需要动态代理,此时创建代理对象);
将 A 的半成品存入 2级缓存,并从 3级缓存删除(避免重复生成);
将 A 半成品注入 B,B 完成属性注入和后续初始化,成为 成品,存入 1级缓存。
4.完成 Bean A 的初始化:
css
从 1级缓存 获取已成品的 B,注入 A;
A 完成属性注入和后续初始化,成为 成品,存入 1级缓存,并从 2级缓存删除。
四、 失效场景
Spring 无法解决循环依赖的核心场景,均源于Bean 初始化流程中无法通过"提前暴露半成品 Bean"机制打破依赖循环,主要集中在以下三类情况:
一、构造器注入导致的循环依赖
这是最典型且 Spring 明确无法解决的场景。
当两个或多个 Bean 通过构造器参数相互依赖时(如 A 的构造器需要 B,B 的构造器需要 A),Spring 初始化 Bean 时需先实例化构造器参数(依赖 Bean),但此时依赖 Bean 同样需要先实例化当前 Bean,形成"实例化阶段"的死循环,无法提前暴露半成品 Bean(半成品 Bean 需先完成实例化,而构造器注入卡在实例化第一步)。
typescript
示例(无法解决)
@Component
public class A {
// A 的构造器依赖 B
public A(B b) {}
}
@Component
public class B {
// B 的构造器依赖 A
public B(A a) {}
}
二、多例(Prototype)Bean 的循环依赖
无论是通过构造器、setter 还是字段注入,只要循环依赖的 Bean 中存在多例(@Scope("prototype"))Bean,Spring 均无法解决。
原因是多例 Bean 不支持"提前暴露半成品 Bean":Spring 对多例 Bean 的策略是"每次获取都重新创建实例",不会将其存入容器的"三级缓存"(用于提前暴露半成品 Bean 的缓存),导致依赖循环时无法获取到半成品实例,只能反复触发新实例创建,最终抛出循环依赖异常。
less
示例(无法解决)
@Component
@Scope("prototype") // 多例 Bean
public class A {
@Autowired
private B b; // setter/字段注入 B
}
@Component
@Scope("prototype") // 多例 Bean
public class B {
@Autowired
private A a; // setter/字段注入 A
}
三、BeanPostProcessor 或 BeanFactoryPostProcessor 参与的循环依赖
若循环依赖的 Bean 中,有一个是 BeanPostProcessor(或其实现类,如 AutowiredAnnotationBeanPostProcessor),或依赖链中包含 BeanFactoryPostProcessor,Spring 可能无法解决。 原因是 BeanPostProcessor 的初始化优先级极高(早于普通 Bean),其依赖的 Bean 会被提前触发初始化,但此时普通 Bean 的"提前暴露"机制尚未完全生效,或 BeanPostProcessor 自身的处理逻辑会中断半成品 Bean 的暴露流程,导致循环依赖无法打破。
less
示例(可能无法解决)
// 自定义 BeanPostProcessor,依赖普通 Bean B
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Autowired
private B b; // 依赖 B
// ... 实现方法
}
// 普通 Bean B 依赖 MyBeanPostProcessor(或其依赖的 Bean)
@Component
public class B {
@Autowired
private A a; // 若 A 又依赖 MyBeanPostProcessor,形成循环
}
总结
Spring 仅能解决**"单例 Bean + setter/字段注入"** 场景的循环依赖(依赖"三级缓存"提前暴露半成品 Bean);上述三类场景因突破了"提前暴露"的前提(构造器卡实例化、多例不存缓存、处理器优先级冲突),均无法解决。
五、关键结论
Spring 解决循环依赖的本质是:在 Bean 实例化后、初始化前,通过三级缓存提前暴露"半成品 Bean",让依赖方先拿到可用的实例,待自身初始化完成后再反哺给被依赖方,最终打破循环。 该机制仅适用于"单例 + setter/字段注入",构造器注入 、多例 Bean ,单例的代理bean通过setter注入 ,设置了@DependsOn注解的bean 等场景因无法提前暴露半成品,均无法解决。