适用读者:被
BeanCurrentlyInCreationException或"三级缓存"绕晕的人。
前置:S07-B02。
实测环境:Spring Boot 3.3.6 / JDK 17(2026-11-05)。
覆盖旧文:974(79) 973(77) = 156 点。
覆盖扩展:E-S07-013(三级缓存各存什么)、E-S07-014(构造器注入为何无解)。
上一篇:S07-J01 · 事务:传播机制与失效的六种原因
下一篇:S07-J03 · Spring Event。
1. 一句话结论
三级缓存是"提前暴露半成品 Bean"的机制,只为解决 setter/字段注入的循环依赖;构造器注入的循环无法解决——Spring Boot 2.6+ 默认直接禁止。
实测(构造器互相注入):
APPLICATION FAILED TO START
The dependencies of some of the beans in the application context form a cycle
Relying upon circular references is discouraged and they are prohibited by default.
... it may be possible to break the cycle automatically by setting
spring.main.allow-circular-references to true
异常链里还有一句关键的:Requested bean is currently in creation: Is there an unresolvable circular reference?
2. 三级缓存各存什么(E-S07-013)
| 缓存 | 内容 | 作用 |
|---|---|---|
一级 singletonObjects |
完整 Bean | 正常获取 |
二级 earlySingletonObjects |
提前暴露的原始 Bean(未完成注入) | 避免重复创建 |
三级 singletonFactories |
ObjectFactory(可生成代理) |
AOP 代理在此生成 |
为什么需要三级而不是两级:
| 只用两级 | 问题 |
|---|---|
| 提前暴露原始对象 | 如果该 Bean 需要 AOP 代理,其他 Bean 拿到的是原始对象,不是代理 → AOP 失效 |
三级缓存的工厂可以"按需生成代理"——这是它存在的真正理由,不是"多缓存一份"。
3. 解决流程(setter 注入)
创建 A → 实例化 A(未注入)→ 把 A 的 ObjectFactory 放入三级缓存
→ 注入 B → 创建 B → 注入 A → 从三级缓存拿到 A(或代理)
→ B 完成 → 注入回 A → A 完成
关键顺序:实例化在前、注入在后——所以 setter/字段注入能"先给个空壳"。
4. 为什么构造器注入无解(E-S07-014)
构造器必须在实例化时就有依赖,没有"半成品"阶段可暴露 → 无法提前暴露 → 循环无法打破。
| 注入方式 | 循环依赖 |
|---|---|
| 字段/setter | ✅ 可解(三级缓存) |
| 构造器 | ❌ 无解(Boot 2.6+ 直接报错) |
这也是一句重要论断的依据:"能靠三级缓存救的循环依赖,本身就是设计问题"——官方从 2.6 起默认禁止(spring.main.allow-circular-references=true 可临时放开)。
5. 正确的处理方式
| 方式 | 说明 |
|---|---|
| 重构依赖方向 | 抽出第三个 Bean 承载公共逻辑(首选) |
@Lazy |
注入代理,延迟到用时才解析 |
| 事件/回调 | 用 Event(J03)解耦反向依赖 |
| 放开开关 | 临时方案,会掩盖设计问题 |
6. 五个误区
| 误区 | 真相 |
|---|---|
| "Spring 能解决所有循环依赖" | 构造器注入不行 |
| "三级缓存只是为了性能" | 核心是提前暴露代理 |
| "两级缓存就够" | 有 AOP 时会拿到原始对象 |
| "循环依赖是正常现象" | Boot 2.6+ 默认禁止 |
"@Lazy 是最佳方案" |
掩盖问题,优先重构 |
7. 自检
- 三级缓存各存什么?为什么第三级必须有?
- 为什么构造器注入的循环无解?
- Boot 2.6+ 对循环依赖的默认行为?
- 四种处理方式里,哪个是首选?
- "两级缓存出问题"的具体场景是什么?
8. 下一篇
S07-J03 · Spring Event:事件发布订阅
循环依赖讲完,下一篇给"解耦反向依赖"的正规工具:事件机制(实测同步在发布线程、异步在 task-1)。
本篇属于系列《S07 · Spring 框架》。旧文来源:A-974/973;扩展点 E-S07-013/014。启动报错为 Spring Boot 3.3.6 实测。