Spring如何解决解决循环依赖问题?

Spring如何解决解决循环依赖问题?

一、什么是循环依赖?

循环依赖是指两个或多个Bean之间相互持有引用,形成一个闭环。例如:

1
2
3
4
5
6
7
8
9
10
11
@Component
public class A {
@Autowired
private B b;
}

@Component
public class B {
@Autowired
private A a;
}

当Spring尝试创建A时,发现需要注入B,于是去创建B;而创建B时又发现需要注入A,这就形成了死循环。如果不加处理,容器将陷入无限递归,最终导致栈溢出。

二、Spring解决循环依赖的前提

Spring解决循环依赖并非万能,它必须满足以下条件:

  1. 作用域为单例(singleton):Spring仅对单例模式的Bean提供循环依赖解决方案。原型(prototype)模式的Bean由于每次创建都是新实例,无法缓存早期暴露的对象,因此遇到循环依赖会直接抛出BeanCurrentlyInCreationException
  2. 通过setter注入(或字段注入):这种注入方式允许对象先通过无参构造器实例化,再设置属性,从而能够提前暴露对象。构造器注入无法解决循环依赖,因为构造器必须在实例化阶段就传入依赖,此时对象还未创建完成,无法提前暴露。

三、Spring解决循环依赖的核心:三级缓存

Spring在DefaultSingletonBeanRegistry中维护了三级缓存,正是这三张表化解了循环依赖的难题。

1
2
3
4
5
6
7
8
9
// DefaultSingletonBeanRegistry.java
/** 一级缓存:单例对象的缓存,存放完全初始化好的 Bean(成品) */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** 二级缓存:早期单例对象的缓存,存放原始的 Bean 对象(半成品,尚未填充属性) */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

/** 三级缓存:单例工厂的缓存,存放 ObjectFactory,可以生成早期对象(提前暴露的引用) */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  • 一级缓存:最终存放已经完成属性填充、初始化等所有步骤的Bean,是给外部使用的完整Bean。
  • 二级缓存:存放实例化完成但尚未属性填充和初始化的原始Bean(或者被AOP代理后的早期对象)。当Bean正在创建时,其他Bean可以从二级缓存中获取它的早期引用。
  • 三级缓存:存放ObjectFactory,这是一个函数式接口,调用其getObject()方法可以获取早期引用。它的作用是延迟代理对象的生成,避免在Bean实例化后立即进行AOP代理,从而破坏循环依赖。

四、源码流程剖析:以A和B互相依赖为例

让我们通过源码一步步跟踪A和B的创建过程。

1. 开始创建A

调用getBean("a"),进入doGetBean,由于A不在缓存中,开始创建流程。A被标记为正在创建(singletonsCurrentlyInCreation.add(beanName))。

2. 实例化A

执行createBeanInstance,通过反射调用A的无参构造器,实例化出一个原始的A对象(此时A的属性b还是null)。

1
2
3
4
5
6
7
// 实例化后,在doCreateBean中执行以下代码
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 将A的早期引用以ObjectFactory的形式添加到三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

addSingletonFactory的代码如下:

1
2
3
4
5
6
7
8
9
10
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}

此时三级缓存中有了一个可以产生A早期引用的工厂。

3. 填充A的属性

继续执行populateBean,尝试为A注入属性b。发现b需要从容器获取,于是调用getBean("b")

4. 创建B

getBean("b")同样进入创建流程。B实例化后,也将自己的ObjectFactory放入三级缓存。然后开始填充B的属性,发现需要注入A。

5. B获取A的引用

B调用getBean("a")。此时A已经在创建中(isSingletonCurrentlyInCreation("a")为true),于是进入getSingleton的缓存获取逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 从二级缓存获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 从三级缓存获取ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject(); // 执行getEarlyBeanReference
// 将对象从三级缓存提升到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}

关键步骤:

  • 一级缓存singletonObjects中还没有A(因为A还没创建完)。
  • 二级缓存earlySingletonObjects中也没有A。
  • 三级缓存singletonFactories中存在A的工厂,于是调用factory.getObject()

getObject()执行的是我们之前放入的Lambda:() -> getEarlyBeanReference(beanName, mbd, bean)getEarlyBeanReference会遍历所有SmartInstantiationAwareBeanPostProcessor,如果存在AOP代理的需求(例如A被@Transactional@Async标记),则会在这里返回代理对象;否则返回原始对象。

1
2
3
4
5
6
7
8
9
10
11
12
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}

假设A不需要代理,则直接返回原始的A对象。B拿到了A的引用(虽然A此时还没完成属性注入,但已经是一个可用的对象了),然后将A的引用注入到自己的属性a中。

B拿到A后,继续完成自己的属性填充、初始化等步骤,最终成为一个完整的Bean。B创建完成后,会被放入一级缓存singletonObjects,并从三级/二级缓存中移除。

6. 回到A的创建

B创建完毕并返回给A的populateBean方法,A将自己的属性b设置为B的引用。随后A执行初始化方法等,也成为一个完整Bean,被放入一级缓存。

至此,循环依赖圆满解决。

五、为什么三级缓存不能简化成二级?

有的同学可能会问:既然二级缓存earlySingletonObjects可以直接存放早期对象,为什么还需要三级缓存singletonFactories?直接用二级缓存存储原始对象不行吗?

答案是:为了处理AOP代理。 如果Bean需要被AOP代理,那么我们希望在循环依赖中,其他Bean注入的应该是代理对象,而不是原始对象。但AOP代理的创建通常是在Bean初始化完成后,通过BeanPostProcessor(如AbstractAutoProxyCreator)的postProcessAfterInitialization生成的。如果等初始化完成再生成代理,那么早期暴露的对象就是原始对象,会导致后续注入的依赖不一致。

因此,Spring引入了三级缓存,通过ObjectFactory实现延迟代理:当其他Bean需要获取早期引用时,才通过工厂方法决定是否生成代理。如果不需要代理,工厂直接返回原始对象;如果需要代理,工厂返回代理对象。这样既保证了循环依赖的正确性,又保证了AOP语义的一致性。

AbstractAutoProxyCreator正是通过实现SmartInstantiationAwareBeanPostProcessorgetEarlyBeanReference方法,在早期暴露时就创建代理,并将代理放入二级缓存。这样后续获取到的都是同一个代理对象。

六、为什么构造器注入无法解决循环依赖?

因为构造器注入要求依赖对象在实例化时就必须传入,也就是说在createBeanInstance阶段就需要依赖对象。而此时对象尚未实例化完成,无法提前暴露自己的早期引用给其他Bean,因此无法打破循环。如果两个Bean通过构造器相互依赖,Spring将抛出BeanCurrentlyInCreationException

七、其他注意事项

  • allowCircularReferences开关:Spring允许通过setAllowCircularReferences(false)关闭循环依赖支持,默认是true。
  • 原型模式下的循环依赖:由于原型Bean每次创建都是新对象,无法缓存,因此检测到循环依赖时会直接抛出异常。
  • 不仅仅是字段注入:setter注入同样适用,因为setter注入也是先实例化再设值。

八、总结

Spring通过三级缓存巧妙地解决了单例模式下setter注入的循环依赖问题:

  • 一级缓存:存放完整Bean。
  • 二级缓存:存放早期暴露的对象(可能是原始对象或代理对象),作为过渡。
  • 三级缓存:存放ObjectFactory,延迟生成早期对象,以支持AOP代理。

当A依赖B,B依赖A时:

  1. A实例化后,将自身工厂放入三级缓存。
  2. A填充属性时发现需要B,转而去创建B。
  3. B实例化后,也将自身工厂放入三级缓存。
  4. B填充属性时发现需要A,从三级缓存拿到A的工厂,生成A的早期引用(原始或代理),注入给自己,B完成创建。
  5. A继续填充属性,得到B的完整引用,最终完成创建。

这一设计既保证了对象的正确创建,又兼顾了AOP等高级特性,是Spring IoC容器最精彩的内核之一。


Spring如何解决解决循环依赖问题?
https://johnjoyjzw.github.io/2024/12/10/Spring解决循环依赖-从源码角度进行全解析/
Author
JiangZW
Posted on
December 10, 2024
Licensed under