Spring三级缓存仅支持单例Bean的属性/字段注入循环依赖,不支持构造器注入、多例(prototype)及request/session作用域循环依赖,因构造器注入在实例化前即需完整依赖,无法进入缓存注册流程。

Spring 的三级缓存机制只对单例(singleton)Bean 的**属性注入型循环依赖**有效,不解决构造器注入循环、多例(prototype)循环,也不适用于字段未初始化就调用的场景。
为什么 constructor 注入会直接失败
构造器注入要求所有依赖在实例化前就“完全可用”,而循环双方都卡在彼此未完成构造的阶段。Spring 连一级缓存都进不去,更不会走到三级缓存逻辑。
-
getBean("A")→ 实例化 A → 需要 B → 调用getBean("B") -
getBean("B")→ 实例化 B → 需要 A → 再次调用getBean("A"),此时 A 尚未放入任何缓存(连三级缓存都没来得及注册),抛出BeanCurrentlyInCreationException
所以只要存在任一环是构造器注入,三级缓存就彻底失效 —— 不是它“没起作用”,而是根本没机会触发。
singletonFactories 是关键,但只在特定时机注册
三级缓存真正起作用的前提是:Spring 必须在 A 刚完成实例化(new A())、尚未填充属性时,就把一个 ObjectFactory 放进 singletonFactories。这个动作发生在 createBeanInstance 之后、populateBean 之前。
- 只有
singletonBean 才会走这个流程;prototype每次都新建,跳过缓存注册 - 如果 A 类上加了
@Lazy,首次getBean("A")不会提前暴露,singletonFactories不会被填充,B 就取不到早期引用 -
ObjectFactory.getObject()返回的是原始对象(非代理),但如果 A 后续需要 AOP 代理,这个早期暴露的对象可能和最终代理不一致 —— Spring 会在代理生成后覆盖二级缓存中的原始对象
常见误判:以为加了 @Autowired 就自动支持循环
实际是否能解,取决于两个 Bean 的创建顺序和注入方式组合。比如:
- A 使用
@Autowired private B b(field 注入),B 使用构造器注入 A → 失败(B 构造时 A 还没准备好) - A 和 B 都用 field 或 setter 注入 → 成功(三级缓存可介入)
- A 是普通 Bean,B 是
@Async或@Transactional增强 Bean → 可能成功,但要注意代理对象是否被正确替换进二级缓存
尤其注意:Lombok 的 @RequiredArgsConstructor 默认生成构造器注入,容易在无意中引入构造循环。
复杂模型下真正该做的不是“硬解”,而是识别边界
当业务模型出现 A→B→C→A 式长链循环,或混合了 @PostConstruct、事件监听、异步回调等行为时,三级缓存已无法兜底。此时强行保留循环,会导致:
- 单元测试难 mock(依赖链太深)
- 启动时序敏感(某个 Bean 初始化早于预期)
- 代理失效(如
@Scheduled方法被注入到未代理对象中)
比“怎么让三级缓存生效”更重要的是:用 @Lazy 打断强依赖,或把共享逻辑抽成无状态工具类 / @Service,让双方都依赖第三方 —— 这些改动往往比调试缓存命中更可靠。










