三级缓存不是通用的“循环依赖解决器”。它只在单例 Bean 已实例化但尚未完成初始化时,协调早期引用和最终代理的一致性;构造器互相依赖时,对象根本无法先创建。
Table of contents
Open Table of contents
三个缓存保存什么
| 缓存 | 内容 |
|---|---|
一级 singletonObjects | 已完成生命周期的单例 |
二级 earlySingletonObjects | 已暴露的早期实例引用 |
三级 singletonFactories | 能生成早期引用的 ObjectFactory |
三级保存工厂而不是直接保存对象,是因为 AOP 后置处理器可能需要返回早期代理。这样其他 Bean 注入的引用与最终暴露引用保持一致。
构造器循环为什么失败
若 A 构造器需要 B,B 构造器又需要 A,容器在任何一个对象实例化前都缺少另一个完整参数。此时没有可放入早期缓存的实例,应该抛出创建中异常。
可以给最小 IoC 容器下载增加 A→B→A 测试,观察递归创建。 真实教学改进应维护“正在创建”集合并报告依赖路径,而不是等栈溢出。
属性循环为什么有时能工作
Setter 或字段注入让容器先调用无参构造器得到 A,再暴露早期引用,随后创建 B 并把 A 注入,最后回头完成 A。但这依赖单例作用域、具体配置和代理行为, Spring Boot 默认也不鼓励依赖循环。
能启动不代表设计合理。A 与 B 互相知道彼此,往往说明职责没有分开,测试也更难隔离。
优先重构循环
- 抽取 A、B 都依赖的第三个服务;
- 让一个方向发布事件,而不是同步回调;
- 把跨步骤状态交给协调器;
- 若确实延迟使用,注入
ObjectProvider并记录理由; - 不要用
@Lazy机械掩盖所有循环。
重构后用业务行为测试证明顺序与事务边界没有改变。删除循环的目标是澄清所有权,而不只是让 Context 变绿。
下一步
阅读配置类、组件扫描与条件装配,理解 BeanDefinition 从哪里进入容器。