Spring仅能解决单例+Setter/字段注入的循环依赖,依靠三级缓存(singletonFactories→earlySingletonObjects→singletonObjects)提前暴露半成品Bean破环;构造器注入因依赖必须在new时就存在,无法暴露引用,直接抛BeanCurrentlyInCreationException;根本原因在于构造器阶段对象尚未实例化,无法进入缓存暴露流程。

直接说清楚三点:Spring 只能解决单例 + Setter/Field 注入的循环依赖,靠的是三级缓存机制;构造器注入的循环依赖无法解决,会抛 BeanCurrentlyInCreationException;根本原因在于创建时机——必须等对象“new 出来”之后,才能提前暴露引用去破环。
一、先讲清前提条件
不是所有循环依赖都能解。必须同时满足:
- Bean 是 singleton(默认作用域)
- 依赖注入方式是 setter 方法或字段注入(
@Autowired写在属性或 setter 上) - 不能是 prototype(多例)或构造器注入(
@Autowired在构造方法参数上)
二、用 A 依赖 B、B 依赖 A 举例说明三级缓存怎么破环
以 getBean("A") 启动为例:
- Spring 实例化 A(调用无参构造),此时 A 是个“空壳”,还没设属性 → 放入三级缓存
singletonFactories - 开始填充 A 的属性,发现依赖 B → 调用
getBean("B") - B 实例化后也放入三级缓存,接着填充属性,发现依赖 A → 再次调用
getBean("A") - 这时 Spring 查一级缓存没 A,查二级缓存也没,就去三级缓存取工厂,执行
getObject()→ 得到 A 的早期引用(未填充属性的半成品)→ 放入二级缓存earlySingletonObjects,并从三级缓存移除 - B 拿到这个 A 的早期引用,完成属性填充和初始化 → 放入一级缓存
singletonObjects - 回到 A,继续填充 B 属性(此时 B 已就绪),A 完成初始化 → 也进一级缓存
三、点明为什么构造器注入不行
因为构造器注入要求:创建 A 前,B 必须已完全创建好;而创建 B 又要求 A 先存在 —— 这是个“鸡生蛋、蛋生鸡”问题,连“new A()”这一步都卡住,根本走不到后续暴露引用的阶段。Spring 没法绕过构造函数去提前给引用,所以直接报错。
立即学习“Java免费学习笔记(深入)”;
四、补充一个实用技巧(面试加分项)
如果真遇到构造器循环依赖又不能改设计,可以用 @Lazy 延迟加载其中一个 Bean。比如在 B 的构造参数上加 @Lazy,这样 A 创建时不会立刻触发 B 的完整初始化,而是生成一个代理对象占位,等真正调用 B 的方法时才去创建,从而打破初始化时的闭环。


















