Spring解决循环依赖
约 2653 字大约 9 分钟
2025-12-10
Spring的循环依赖解决方案
什么是循环依赖
在Spring框架中,循环依赖是指两个或多个Bean之间相互依赖,形成一个闭环。例如,BeanA依赖与BeanB,而BeanB又依赖于BeanA,导致Spring在创建Bean的时候无法确定先初始化哪一个。
@Service
public class ServiceA {
private ServiceB serviceB;
// 构造函数注入:直接依赖ServiceB
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private ServiceA serviceA;
// 构造函数注入:直接依赖ServiceA
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}Spring的循环依赖解决策略
基本的解决思路
Spring通过三级缓存机制解决Setter/属性注入的循环依赖:
- 一级缓存(单例池):存放已经完成初始化好的Bean;
- 二级缓存:存放早期暴露的Bean(已实例化但未填充属性);
- 三级缓存:存放Bean工厂对象,用于生成早期Bean;
解决的流程:
- 创建A的实力,放入三级缓存;
- 填充A的属性时发现需要B,转而创建B;
- 创建B的实例,填充属性时发现需要A,从三级缓存中获取A的早期引用(此时A未初始化完成);
- B完成初始化,放入一级缓存;
- A获得B的实力,完成初始化,放入一级缓存;
循环依赖的源码分析
首先需要了解三级缓存分别对应的是什么属性:
// 一级缓存(单例池,所有创建好的单例Bean的对象,无论是代理对象还是实例对象都会存放在一级缓存中)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
// 二级缓存(主要是存放利用三级缓存生成的对象)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
// 三级缓存存放的是一个生成Bean的方式
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);然后我们需要了解下如果需要从IOC容器中获取一个Bean的时候它的流程是怎么样的,也就是getSingleton()方法的具体逻辑了:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// singleObjects就是单例池(也就是一级缓存池),当需要从IOC中获取一个Bean的时候,会先从单例池中查找
Object singletonObject = this.singletonObjects.get(beanName);
// 如果单例池中不存在这个Bean且当前这个单例Bean正在创建
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
// 从早期的单例Bean中查询这个Bean(二级缓存中查找)
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果从二级缓存中查找的结果仍然为空
if (singletonObject == null && allowEarlyReference) {
synchronized(this.singletonObjects) {
// DCL双重检查机制,再次判断单例池中是否存在这个单例Bean
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
// DCL检查后发现单例池和二级缓存中都不存在这个Bean
if (singletonObject == null)
// 从单例Bean的工厂(三级缓存)中获取到这个Bean的创建方式
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 如果这个创建工厂不为空,就执行创建工厂的方法来生成这个Bean
singletonObject = singletonFactory.getObject();
// 将生成好的这个Bean放入到二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 从三级缓存中移除该Bean的工厂
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
// 当前这个Bean正在创建或者单例池中已经存在这个Bean了(返回null或单例池中的对象)
return singletonObject;
}了解了上面的基础之后,我们就可以来看在初始化Bean的过程中,是如何通过这个三级缓存来解决循环依赖的。同样的,来看初始化Bean的过程,也就是doCreateBean()方法的具体逻辑:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
// 根据RootBeanDefinition来创建Bean的实例对象(得到的是BeanWrapper对象,包含Bean的实例对象和类型信息)
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = this.createBeanInstance(beanName, mbd, args);
}
// 获取到Bean的实例对象和类型信息
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// 找到所有实现了MergedBeanDefinitionProstProcessor执行其中的postProcessMergedBeanDefinition()方法
synchronized(mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
} catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex);
}
mbd.markAsPostProcessed();
}
}
/**
* 很重要,解决循环依赖的关键点
* 这一步判断条件为三个:当前Bean是单例Bean && 允许循环依赖的配置是开启的(SpringBoot 2.6.x版本是默认关闭的)&& 当前Bean正在创建
*/
boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
/**
* 将当Bean的一个创建方法存储到三级缓存中(每个Bean在属性注入之前都需要完成的事情)
* 这一步是非常有必要的:因为这保证在循环不会出现死循环
* 假如实例化流程为: 初始化A (发现依赖B)--> 初始化B(发现依赖A)此时会B在初始化的过程中可以从三级缓存中拿到A的ObjectFactory
* 它就可调用ObjectFactory#getObject()方法生成一个A,此时B就可以完成初始化了,也就跳出了死循环
*/
this.addSingletonFactory(beanName, () -> this.getEarlyBeanReference(beanName, mbd, bean));
}
Object exposedObject = bean;
try {
// 这里是对属性进行填充(通过AutowiredAnnotationBeanPostProcessor实现@Autowired注解的属性填充的)
this.populateBean(beanName, mbd, instanceWrapper);
// 初始化这个Bean
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
} catch (Throwable var18) {
if (var18 instanceof BeanCreationException bce) {
if (beanName.equals(bce.getBeanName())) {
throw bce;
}
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, var18.getMessage(), var18);
}
/**
* 重点:循环依赖的解决步骤
* 这一步判断的是要不要去解决循环依赖
*/
if (earlySingletonExposure) {
// 再次从IOC容器中获取到这个Bean(要注意的是上面初始化完成的exposedObject对象还没有进入单例池中)
Object earlySingletonReference = this.getSingleton(beanName, false);
/**
* 这里判断的必要性:A实例化过程中发现依赖了B --> 实例化B的过程中发现了依赖A --> 调用三级缓存中方法生成A --> B实例化结束
* 此时流程会再次回到实例化A的流程,但是此时二级缓存中已经有了BeanA的一个实例
*/
if (earlySingletonReference != null) {
/**
* 这一步的判断也是非常关键的:因为此时二级缓存中已经有这个BeanA的实例了,但是上面BeanA初始化完成后也生成了实例
* 如果两个实例不相等(初始化的过程中对A进行了修改,例如使用BeanPostProcessor进行了修改),那完了
* 神仙来都得懵逼,到底听谁的,以什么为准,所以能做的就是直接抛出异常
*/
if (exposedObject == bean) {
/**
* 这一步也是很重要的,因为这个BeanA有可能是需要AOP代理的,所以它经过三级缓存中的工厂生成的是BeanA的代理对象
* 但是我们按照上面的步骤得到的是BeanA的实例化对象
* 所以这一步必须要向上面的方法返回的代理对象的,这个样代理对象才会被存储到单例池中(不能返回实例对象,不然会让AOP失效的)
*/
exposedObject = earlySingletonReference;
} else if (!this.allowRawInjectionDespiteWrapping && this.hasDependentBean(beanName)) {
String[] dependentBeans = this.getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet(dependentBeans.length);
for(String dependentBean : dependentBeans) {
if (!this.removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
try {
// 如果这个Bean实现了DisposableBean接口的话,将它注册到缓存中,以便在要销毁Bean的时候执行对应的destroy()方法
this.registerDisposableBeanIfNecessary(beanName, bean, mbd);
// 返回这个实例化好的Bean(回到了createBean()中)
return exposedObject;
} catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
}所以关键的要点在于:
- 每个Bean在正式进行初始化之前都会在三级缓存中存放一个创建自己的方式(ObjectFactory对象)。
- 假如BeanA在创建的时候发现依赖了BeanB(此时三级缓存中已经有了BeanA的ObjectFactory对象),就会去实例化BeanB,所以BeanB在调用
getSingleton()方法的时候就从三级缓存中找到BeanA的ObjectFactory,此时就会调用BeanA的ObjectFactory#getObject()方法得到一个BeanA的实例,然后存储到二级缓存中。
所以解决循环依赖的核心就是三级缓存,那么我们就来聊一下三级缓存具体缓存的是什么东西?
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
// 这个解析RootBeanDefinition后生成的空白对象
Object exposedObject = bean;
/**
* 这里是判断这个Bean是否是合成的(Spring框架中的Bean)
* 所有我们用户定义的Bean都会执行SmartInstantiationAwareBeanPostProcessor
* 的getEarlyBeanReference()方法
*/
if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
// 从所有的InstantiationAwareBeanPostProcessor中找到SmartInstantiationAwareBeanPostProcessor类型的Bean并调用他们的getEarlyBeanReference()方法
for(SmartInstantiationAwareBeanPostProcessor bp : this.getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}而AOP的实现方式正是AbstractAutoProxyCreator,他就是SmartInstantiationAwareBeanPostProcessor的子类,所以对于代理对象也是可以在这里一并生成的。
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
this.earlyBeanReferences.put(cacheKey, bean);
// 如果这个Bean需要代理就生成代理对象,如果不需要就返回原始Bean
return this.wrapIfNecessary(bean, beanName, cacheKey);
}总结
所以解决循环依赖的关键点是三级缓存,但是我们仍然需要明确为什么需要三级缓存。
仅仅使用一级缓存是否可以解决循环依赖?
答案是不可以的,看下面的流程:
- BeanA在进行初始化,发现依赖了BeanB,此时从IOC容器中获取到的BeanB的实例为空;
- 开始实例化BeanB,发现依赖了BeanA,此时从IOC容器中获取到:
- 如果获取到的是null,无法解决循环依赖的问题,死循环仍然存在;
- 如果获取到时BeanA初始化一半的对象,的确是可以解决死循环的问题,如果调用BeanB中的BeanA的某个方法极其容易出现NullPointException异常;
所以仅仅使用一级缓存是无法解决循环依赖的问题的,它会导致BeanB在初始化的时候注入的BeanA是个半成品对象。
仅仅使用二级缓存是否可以解决循环依赖?
答案是理论上是可以的,但是仅仅能双方都是非AOP代理对象的循环依赖;
我们可以在初始化BeanA开始,像二级缓存中放入原始的实例对象。此时如何BeanB开始实例化的时候就可以从二级缓存中获取到BeanA的实例对象