spring仅支持单例bean的setter/字段注入循环依赖,通过三级缓存(singletonfactories→earlysingletonobjects→singletonobjects)实现“提前暴露”半成品bean;构造器注入因依赖必须在实例化时就位,无缓冲窗口,故不支持,抛beancurrentlyincreationexception。

Spring 只能解决单例 Bean 的 Setter 或字段注入型循环依赖,靠的就是三级缓存协同配合的“提前暴露”策略。它不靠魔法,而是一套精巧的状态分层与时机控制:在对象刚实例化、还没填充属性时,就允许被其他 Bean 提前引用——这正是打破死锁的关键。
为什么只有 Setter/字段注入能解,构造器不行?
构造器注入要求在 new 出对象那一刻,所有依赖必须就位。但此时 A 还没完成创建,B 更不可能拿到一个完整的 A 来传进自己的构造器。Spring 没法在构造阶段“塞进去”一个半成品,所以直接抛 BeanCurrentlyInCreationException。
- Setter/字段注入发生在实例化之后、初始化之前,留出了“暴露早期引用”的时间窗口
- 构造器注入把依赖绑定压到最前端,没有缓冲空间,三级缓存无从介入
- 这也是 Spring 明确不支持构造器循环依赖的根本原因,不是缺陷,是设计边界
三级缓存各自管什么?不是堆内存,是状态接力
它们不是并列存储,而是一个随 Bean 生命周期推进逐步移交的“状态流水线”:
- 三级缓存(singletonFactories):存的是 ObjectFactory,本质是“怎么造出早期 A”的 lambda 表达式。只在实例化后、属性填充前写入,是“可生成但未生成”的待命态
- 二级缓存(earlySingletonObjects):存的是调用 ObjectFactory 得到的原始对象(比如 A 的空壳),已实例化但属性为空、未走 @PostConstruct。它由三级缓存“触发生成”,用于实际注入
- 一级缓存(singletonObjects):存的是最终版 Bean,完整走完实例化 → 属性填充 → 初始化 → Aware 回调 → 后置处理器(含 AOP 代理)全过程
三者之间有明确的转移规则:一旦某 Bean 进入一级缓存,对应二、三级缓存中的记录就会被清除,避免脏引用。
以 A ↔ B 为例,看缓存如何“接住”彼此
整个过程像一场精密的双人协作:
- A 开始创建:调用无参构造器生成空 A → 把 A 的 ObjectFactory 放进三级缓存
- A 填充属性时发现要 B → 触发 getBean(B)
- B 开始创建:生成空 B → 把 B 的 ObjectFactory 放进三级缓存
- B 填充属性时发现要 A → 查一级缓存(无)、查二级缓存(无)、查三级缓存(命中 A 的工厂)→ 调用工厂生成早期 A → 放入二级缓存 → 从三级缓存移除 A 工厂 → 将该早期 A 注入 B
- B 完成后续流程,放入一级缓存;A 继续执行,从一级缓存拿到完整 B,注入并完成自身初始化,也进入一级缓存
原型 Bean 为什么一定失败?
因为原型 Bean 每次 getBean 都新建,Spring 不做任何缓存。当 B 在创建过程中找 A,即使 A 正在创建中,Spring 也不会复用那个“半成品 A”,而是又开一个新线程/新流程去建 A —— 导致无限递归或并发异常。没有缓存,就没有“暴露”基础,三级缓存机制自然失效。











