Spring通过三级缓存机制解决单例Bean的循环依赖问题,核心在于提前暴露“半成品”对象。当Bean A依赖Bean B,而Bean B又依赖A时,Spring在A实例化后将其ObjectFactory放入三级缓存(singletonFactories),B在创建过程中通过该工厂获取A的原始或代理实例,完成自身初始化并放入一级缓存(singletonObjects),随后A再注入已初始化的B,最终双方都完成创建。此机制依赖Bean生命周期的分阶段处理:实例化→放入三级缓存→属性填充→初始化→升级至一级缓存。二级缓存(earlySingletonObjects)用于存放从三级缓存中取出的早期暴露对象,避免重复创建代理。该方案仅适用于单例作用域下的setter或字段注入,无法解决构造器注入的循环依赖,因为构造器要求所有依赖在实例化时即就绪,无法提前暴露半成品对象。构造器注入导致的循环依赖会直接抛出BeanCurrentlyInCreationException,这实则是设计警示,提示模块耦合过紧。尽管Spring能处理setter循环依赖,但应视为代码“异味”:它增加理解与测试难度,可能导致@PostConstruct中访问未初始化Bean引发运行时异常,且不利于重构。最佳实践包括:优先使用构造器注入以暴露设计问题;拆分职责、引入接口或门面模式解耦;采用事件驱动

Spring解决循环依赖的核心在于其三级缓存机制,结合提前暴露的单例对象,打破了对象创建的僵局。简单来说,当一个Bean A依赖Bean B,同时Bean B又依赖Bean A时,Spring会在Bean A实例化但未完全初始化(属性填充)时,将其“半成品”状态提前放入一个缓存中,供Bean B引用,从而允许Bean B完成初始化,最终Bean A也能顺利完成初始化。
要深入理解Spring如何优雅地处理循环依赖,我们得把目光投向它内部的Bean生命周期管理和那套精妙的“三级缓存”机制。这东西听起来有点玄乎,但实际上它解决的是一个经典的两难问题:当两个或多个Bean互相引用时,到底谁先完全准备好?
想象一下,我们有两个单例Bean:
ServiceA
ServiceB
ServiceB
ServiceA
ServiceA
ServiceA
ServiceA
ServiceA
ServiceB
ServiceA
ServiceA
singletonFactories
ObjectFactory
ObjectFactory
ServiceA
ServiceA
ServiceB
ServiceA
ServiceB
ServiceB
ServiceB
ServiceB
ServiceB
ServiceB
ServiceB
ServiceB
ServiceA
ServiceB
ServiceA
ServiceA
ServiceA
ServiceA
ObjectFactory
ServiceA
ServiceA
ServiceB
ServiceB
ServiceA
ServiceB
singletonObjects
ServiceA
ServiceB
ServiceA
ServiceA
ServiceB
ServiceA
这三级缓存具体是什么?
singletonObjects
earlySingletonObjects
singletonFactories
ObjectFactory
ObjectFactory
这种机制巧妙地利用了Bean的生命周期阶段,在Bean实例化后、属性注入前,就将其“半成品”暴露出来,从而打破了循环引用的死锁。但需要注意的是,这种机制主要针对单例Bean的setter注入和字段注入有效。对于构造器注入的循环依赖,Spring是无法解决的,因为它无法在构造器执行完成前就暴露一个“半成品”对象。
这是一个很实际的问题,尤其是在提倡“构造器注入优先”的当下。答案其实很简单,也很直接:Spring的循环依赖解决机制,也就是我们前面提到的三级缓存,其核心在于Bean的“提前暴露”。这个“提前暴露”发生在一个Bean被实例化之后,但在其所有依赖被注入之前。
当使用构造器注入时,一个Bean的实例化过程本身就需要它的所有依赖都准备就绪。换句话说,
ServiceA
ServiceB
ServiceB
ServiceA
举个例子:
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) { // 构造器需要ServiceB
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) { // 构造器需要ServiceA
this.serviceA = serviceA;
}
}Spring在尝试创建
ServiceA
ServiceB
ServiceA
ServiceB
ServiceB
ServiceA
ServiceA
BeanCurrentlyInCreationException
这并不是Spring的缺陷,而是构造器注入本身的特性所决定的。它强制要求所有依赖在对象构造时就已存在,这使得它在处理循环依赖时变得无能为力。因此,在设计系统时,如果发现构造器注入导致循环依赖,这通常是一个代码设计上的“异味”,暗示着模块之间的职责划分可能不够清晰,或者耦合过于紧密,需要重新审视。
虽然Spring能够优雅地解决大多数单例Bean的setter/field注入循环依赖,但这并不意味着我们应该忽视它。循环依赖,即使被框架解决了,也常常是代码设计中潜在问题的信号。
潜在问题:
@PostConstruct
NullPointerException
最佳实践:
ObjectProvider
Provider
@Lazy
ObjectProvider
Provider
@PostConstruct
@PostConstruct
以上就是Spring 如何解决循环依赖问题?的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号