spring不支持原型作用域+构造器注入的循环依赖,根本原因是:原型bean不进三级缓存,无法提前暴露半成品;构造器注入要求依赖一步到位,无法分步解耦;且该组合违背spring设计哲学,官方明确拒绝支持。

一、原型 Bean 不进三级缓存,无“提前暴露”机会
Spring 解决单例循环依赖的核心是**三级缓存**(尤其是二级 early singleton objects):它允许一个 Bean 在实例化后、属性填充前,先把“半成品”对象提前暴露出来供其他 Bean 注入。但这个机制只对 singleton 有效。
原型 Bean 每次 getBean() 都新建实例,Spring 不会把它放进任何缓存——意味着 A 正在构造时,B 想要依赖 A,Spring 连“临时引用”都拿不出来,只能硬着头皮继续创建 A,结果立刻撞上自己正在创建的 A,死锁。
二、构造器注入要求“完整依赖就位”,无法分步解耦
构造器注入必须在 new 实例那一刻就把所有依赖传进去。而原型模式下:
- A 的构造器要 B → 去创建 B
- B 的构造器要 A → 再去创建 A
- 此时 A 还没构造完,更没返回,连“正在创建中”的标记都没法复用(因为原型不登记)
没有缓存 + 没有中间态 + 依赖必须一步到位 → 只能无限递归或当场报错。Spring 选择后者,避免栈溢出或线程卡死。
三、Spring 官方明确不支持,属于设计边界
这不是 bug,是文档级约定。Spring 的设计哲学是:原型 Bean 应该轻量、无状态、彼此独立。一旦出现原型间的构造器循环依赖,大概率说明:
- 业务逻辑耦合过重(比如两个 service 都需要对方才能初始化)
- 作用域误用(本该是 singleton 的却被标成 prototype)
- 职责划分不清(该拆分的类没拆,硬塞进构造器)
所以 Spring 直接抛错,逼你回头重构,而不是用黑盒机制勉强兜住一个危险设计。
不复杂但容易忽略:只要把任意一个 Bean 改成 singleton,或把构造器注入换成 @Lazy + setter/field 注入,问题就消失——这正说明问题不在“能不能”,而在“该不该”。











