
spring默认采用单例(singleton)作用域,因此同一bean类在整个ioc容器中仅存在一个共享实例;当通过getbean()多次获取时,返回的是同一个对象引用,而非新实例——这正是循环依赖能被解决的前提。
spring默认采用单例(singleton)作用域,因此同一bean类在整个ioc容器中仅存在一个共享实例;当通过getbean()多次获取时,返回的是同一个对象引用,而非新实例——这正是循环依赖能被解决的前提。
在您提供的代码中,DependencyA和DependencyB均未显式声明作用域,因此遵循Spring默认行为:@Scope("singleton")。这意味着:
- 容器启动时,Spring仅创建每个Bean的唯一实例;
- 所有依赖注入、所有getBean()调用均复用该实例;
- 构造函数仅执行一次(如输出所示:“I am constructor of Dependency A” 和 “I am constructor of Dependency B” 各出现一次);
- applicationContext.getBean(DependencyA.class) 与 applicationContext.getBean(DependencyB.class) 中隐式触发的依赖解析,均指向已创建或正在创建中的同一份单例对象。
✅ 为什么构造函数不重复执行?
因为Spring的Bean生命周期管理严格区分“实例化”与“依赖注入”两个阶段:
- 实例化阶段:Spring先调用new DependencyA()和new DependencyB(),生成原始对象(此时属性为空);
- 提前暴露(Early Exposure):将尚未完成属性填充的“半成品”Bean工厂(ObjectFactory)放入三级缓存(singletonFactories),供其他Bean提前引用;
- 依赖注入阶段:DependencyA需注入DependencyB → Spring从三级缓存中获取其早期引用 → 触发DependencyB的setDependencyA();同理,DependencyB注入时也复用已存在的DependencyA实例;
- 初始化完成:两Bean均完成setter注入后,移入一级缓存(singletonObjects),对外提供最终可用的单例Bean。
这就是为何getBean(DependencyB.class)不再触发构造:DependencyB实例已在解析DependencyA依赖时提前创建并缓存,后续直接从一级缓存中返回。
? 关键机制:三级缓存协同工作
| 缓存层级 | 名称 | 存储内容 | 作用 |
|---|---|---|---|
| 一级缓存 | singletonObjects | 完全初始化完毕的Bean(成品) | 对外提供最终可用的单例Bean |
| 二级缓存 | earlySingletonObjects | 提前曝光的、已完成实例化但未完成初始化的Bean(半成品) | 避免重复创建,支持早期引用 |
| 三级缓存 | singletonFactories | ObjectFactory工厂对象(用于动态生成早期Bean) | 解决“正在创建中”的循环引用问题,是打破死锁的核心 |
⚠️ 注意:该机制仅适用于单例 + setter/field注入场景。构造器注入因无法“先实例化后注入”,无法利用三级缓存,故必然报BeanCurrentlyInCreationException;而prototype作用域Bean不参与缓存,每次getBean()都新建实例,也无法解决循环依赖。
? 验证单例共享行为(可选实验)
若希望观察构造函数被多次调用,可显式指定原型作用域:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // ← 添加此注解
class DependencyB {
// ...
}此时运行将输出两次“I am constructor of Dependency B”,且循环依赖会失败——印证了单例共享是Spring解决循环依赖的必要前提。
✅ 总结
- Spring通过单例作用域 + 三级缓存 + setter/field延迟注入三者协同,安全化解单例Bean间的循环依赖;
- getBean()不是“创建Bean”,而是“获取已管理的Bean实例”,复用性由作用域决定;
- 理解singletonObjects(成品)、earlySingletonObjects(半成品)、singletonFactories(工厂)的职责边界,是掌握Spring循环依赖解决方案的核心;
- 实际开发中,应优先避免循环依赖;若不可避免,推荐使用@Lazy(构造器注入场景)或重构为事件驱动/接口解耦,而非依赖框架“兜底”。

















