二十四、创建Bean之循环依赖

创建Bean之循环依赖

doCreateBean()的第三步:循环依赖处理,其他步骤已经在前边介绍了。

循环依赖并不仅仅只是在 doCreateBean(...) 方法中处理,而是在整个加载 bean 的过程中都有涉及。所以,本文内容并不仅仅只局限于 doCreateBean(...) 方法,而是从整个 Bean 的加载过程进行分析。

什么是循环依赖

循环依赖,其实就是循环引用,就是两个或者两个以上的 bean 互相引用对方,最终形成一个闭环,如 A 依赖 B,B 依赖 C,C 依赖 A。

其实就是一个死循环的过程,在初始化 A 的时候发现引用了 B,这时就会去初始化 B,然后又发现 B 引用 C,跑去初始化 C,初始化 C 的时候发现引用了 A,则又会去初始化 A,依次循环永不退出,除非有终结条件

循环依赖的情况有两种:

  • 构造器的循环依赖。
  • field属性的循环依赖。

:对于构造器的循环依赖,Spring 是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖。

在《开启Bean的加载》中已说到:Spring 只解决 scope 为 singleton 的循环依赖。对于scope 为 prototype 的 bean ,Spring 无法解决,直接抛出 BeanCurrentlyInCreationException 异常。所以本文说的解决循环依赖就是解决scope为singleton的循环依赖。

解决循环依赖

getSingleton()方法

从加载bean的最初始方法开始:AbstractBeanFactory.getBean()—>AbstractBeanFactory.doGetBean().

doGetBean(...) 方法中,首先会根据 beanName 从单例 bean 缓存中获取,如果不为空则直接返回

1
2
3
// AbstractBeanFactory.java

Object sharedInstance = getSingleton(beanName);

调用 getSingleton(String beanName, boolean allowEarlyReference) 方法,从单例缓存中获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// DefaultSingletonBeanRegistry.java
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从单例缓冲中加载 bean,//先从一级缓存拿
//singletonObjects 就是Spring内部用来存放单例Bean的对象池,key为beanName,value为Bean
Object singletonObject = this.singletonObjects.get(beanName);
// 缓存中的 bean 为空,且当前 bean 正在创建,属性还没有DI(依赖注入)
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//从二级缓存中拿
// earlySingletonObjects 是早期单例Bean的缓存池,此时Bean已经被创建(newInstance),但是还没有完成初始化
singletonObject = this.earlySingletonObjects.get(beanName);
//如果还拿不到,并且允许bean提前引用(解决循环依赖)
if (singletonObject == null && allowEarlyReference) {
// 加锁
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
//从三级缓存中拿到对象工厂
//singletonFactories 单例工厂的缓存,key为beanName,value 为ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//从工厂中拿到对象
singletonObject = singletonFactory.getObject();
//升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 从 singletonFactories 中移除对应的 ObjectFactory
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}

这个方法主要是从三个缓存中获取,分别是:singletonObjectsearlySingletonObjectssingletonFactories

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// DefaultSingletonBeanRegistry.java

/**
* 一级缓存,存放的是单例 bean 的映射。
* 注意,这里的 bean 是已经创建完成的。
* 对应关系为 bean name --> bean instance
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/**
* 二级缓存,存放的是未初始化完的 bean,对应关系也是 bean name --> bean instance。
* 它与 {@link #singletonObjects} 区别在于, 它自己存放的 bean 不一定是完整。
* 这个 Map 也是【循环依赖】的关键所在。
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/**
* 三级缓存,存放的是 ObjectFactory,可以理解为创建未初始化完的 bean 的 factory ,最终添加到二级缓存 {@link earlySingletonObjects} 中
* 对应关系是 bean name --> ObjectFactory
* 这个 Map 也是【循环依赖】的关键所在。
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

以上三个缓存变量是解决singleton bean的关键,成为三级缓存

一级缓存为singletonObjects,二级缓存为earlySingletonObjects,三级缓存为singletonFactories

getSingleton()方法中其中涉及了一个isSingletonCurrentlyInCreation(String beanName)方法和allowEarlyReference变量:

  • isSingletonCurrentlyInCreation(String beanName)方法:判断当前 singleton bean 是否处于创建中。bean 处于创建中,也就是说 bean 在初始化但是没有完成初始化,有一个这样的过程其实和 Spring 解决 bean 循环依赖的理念相辅相成。因为 Spring 解决 singleton bean 的核心就在于提前曝光 bean

  • allowEarlyReference 变量:从字面意思上面理解就是允许提前拿到引用。其实真正的意思是,是否允许从 singletonFactories 缓存中通过 getObject() 方法,拿到对象。为什么会有这样一个字段呢?原因就在于 singletonFactories 才是 Spring 解决 singleton bean 的诀窍所在

getSingleton(String beanName, boolean allowEarlyReference)方法,过程如下:

  • 首先,从一级缓存 singletonObjects 获取。

  • 如果没有,且当前指定的 beanName 正在创建,就再从二级缓存 earlySingletonObjects 中获取。

  • 如果还是没有获取到,并且允许 singletonFactories 通过 getObject() 获取,则从三级缓存 singletonFactories 获取。如果获取到,则通过其 getObject() 方法,获取对象,并将其加入到二级缓存 earlySingletonObjects 中,并从三级缓存 singletonFactories 删除:

    1
    2
    3
    4
    5
    // DefaultSingletonBeanRegistry.java

    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);

    这样,就从三级缓存升级到二级缓存了。

    所以,二级缓存存在的意义:就是缓存三级缓存中的 ObjectFactory 的 getObject() 方法的执行结果,提早曝光的单例 Bean 对象。

addSingletonFactory()方法

上面是从缓存中获取,但是缓存中的数据从哪里添加进来的呢?一直往下跟会发现在 AbstractAutowireCapableBeanFactory 的 doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) 方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// AbstractAutowireCapableBeanFactory.java

boolean earlySingletonExposure = (mbd.isSingleton() && // 单例模式
this.allowCircularReferences && // 运行循环依赖
isSingletonCurrentlyInCreation(beanName));// 当前单例 bean 是否正在被创建
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 提前将创建的 bean 实例加入到 singletonFactories 中
// 这里是为了后期避免循环依赖
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

当一个Bean满足单例的运行提前暴露bean当前bean正在创建三个条件,就将它添加到缓存中。

addSingletonFactory()如下:

1
2
3
4
5
6
7
8
9
10
11
12
// DefaultSingletonBeanRegistry.java

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);
}
}
}
  • 从这段代码我们可以看出,singletonFactories 这个三级缓存才是解决 Spring Bean 循环依赖的诀窍所在。同时这段代码发生在 createBeanInstance(...) 方法之后,也就是说这个 bean 其实已经被创建出来了,但是它还不是很完美(没有进行属性填充和初始化),而这对于其他依赖它的对象而言已经足够了(可以根据对象引用定位到堆中对象),能够被认出来了。所以 Spring 在这个时候,选择将该对象提前曝光出来让大家认识认识。

另外,addSingletonFactory()中的回调函数getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) 方法也非常重要,这里会创建早期初始化 Bean 可能存在的 AOP 代理等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// AbstractAutowireCapableBeanFactory.java

/**
* 对创建的早期半成品(未初始化)的 Bean 处理引用
* 例如说,AOP 就是在这里动态织入,创建其代理 Bean 返回
*/
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;
}
  • 这也是为什么 Spring 需要额外增加 singletonFactories 三级缓存的原因,解决 Spring 循环依赖情况下的 Bean 存在动态代理等情况,不然循环注入到别人的 Bean 就是原始的,而不是经过动态代理的!
addSingleton()方法

二级缓存earlySingletonObjects、三级缓存singletonFactory如上设置,一级缓存如何设置呢?

在类 DefaultSingletonBeanRegistry 中,可以发现这个 addSingleton(String beanName, Object singletonObject) 方法:

1
2
3
4
5
6
7
8
9
10
// DefaultSingletonBeanRegistry.java

protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
  • 添加至一级缓存,同时从二级、三级缓存中删除。

    该方法何时被引用的?之前介绍《各scope的Bean创建》中,doGetBean()方法中处理不同scope时,如果是singleton,则调用getSingleton(),和上边getSingleton()不同。

    getSingleton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// AbstractBeanFactory.java

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//....
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
//.....
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}

总结

最上边循环依赖Spring解决过程如下:

  • 首先 A 完成初始化第一步并将自己提前曝光出来(通过 ObjectFactory 将自己提前曝光),在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来。
  • 然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来。
  • 这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A),这个时候由于 A 已经添加至缓存中(一般都是添加至三级缓存 singletonFactories ),通过 ObjectFactory 提前曝光,所以可以通过 ObjectFactory.getObject() 方法来拿到 A 对象,C 拿到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中。
  • 回到 B ,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。到这里整个链路就已经完成了初始化过程了。

处理循坏依赖


二十四、创建Bean之循环依赖
http://www.muzili.ren/2022/06/11/创建Bean之循环依赖/
作者
jievhaha
发布于
2022年6月11日
许可协议