Spring源码分析(2)——Spring解决循环依赖

分析Spring是怎么解决循环依赖的

一、什么是循环依赖

当A拥有B属性,B拥有C属性,C拥有A属性,这时就会存在循环依赖

Class A+ getB()+ setB()Class B+ getC()+ setC()Class C+ getA()+ setA()
<use>
<use>
<use>

二、先说结论

  • 单例作用域的setter循环依赖,能够解决

  • 通过提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的bean来完成的,从而使其他bean能引用到该bean。这个过程充分利用了三级缓存:singletonFactory->earlySingletonObjects->singletonObjects

  • 单例作用域的构造器循环依赖,不能解决

  • prototype作用域的循环依赖,不能解决

三、 能够解决的循环依赖

  • 单例作用域的setter循环依赖,能够解决

A拥有B属性,B拥有C属性,C拥有A属性,通过提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的bean来完成的,而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean。

  • 单例作用域的构造器循环依赖,不能解决

A创建时需要B,B创建时需要C,C创建时需要A,最终形成一个环,没办法创建。

  • prototype作用域的循环依赖,不能解决

Spring不缓存prototype作用域的bean,因此无法提前暴露一个创建中的bean。

四、Spring怎么解决循环依赖

4.1 单例模式的setter循环依赖

A拥有B属性,B拥有C属性,C拥有A属性的情况,通过提前暴露刚完成构造器注入但未完成其他步骤(如属性填充)的bean来完成的,而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean。

  • Spring容器创建单例A时,首先根据构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的bean,并将A标识符放到“当前创建bean池”,然后进行setter注入B

  • Spring容器创建单例B时,首先根据构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的bean,并将B标识符放到“当前创建bean池”,然后进行setter注入C

  • Spring容器创建单例C时,首先根据构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的bean,并将C标识符放到“当前创建bean池”,然后进行setter注入A。进行注入A时由于提前暴露了“ObjectFactory”工厂,从而使用它返回提前暴露一个创建中的bean。

  • 最后再依赖注入B和A,完成setter注入

Spring为了解决单例的循环依赖问题,使用了三级缓存,如下所示:

  • singletonFactories:用于保存BeanName和创建bean的工厂之间的关系,也就是刚才所说的“ObjectFactory”。当bean刚实例化完但还没填充属性时,就会与一些工厂代码一起放入到这个map中。(AOP就是在这段工厂代码中将advice动态织入bean中)

  • earlySingletonObjects:和singletonFactories类似,也是保存的已实例化完但还未填充属性的bean。与singletonFactories不同的是,在从singletonFactories获取bean后,会将其存储到earlySingletonObjects中,然后从singletonFactories移除该bean,之后在要获取该bean就直接从earlySingletonObjects获取。这是因为从singletonFactories获取bean过程中需要调用singletonFactory.getObject(),这里还有一些操作,这样可以进一步提升性能。

  • singletonObjects:用于保存已实例化并且已进行属性填充等操作的、已加载完成的bean。

    DefaultSingletonBeanRegistry.java
    1
    2
    3
    4
    5
    6
    7
    8
    /** Cache of singleton objects: bean name --> bean instance */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /** Cache of singleton factories: bean name --> ObjectFactory */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    /** Cache of early singleton objects: bean name --> bean instance */
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

对三级缓存的调用可以参考如下代码,从代码中可见,当从缓存中获取单例时,经历了如下过程:singletonObjects->earlySingletonObjects->singletonFactory,充分使用了三级缓存。

DefaultSingletonBeanRegistry.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}

bean的创建过程会将bean放入三级缓存,放入顺序刚好和获取的顺序相反:singletonFactory->earlySingletonObjects->singletonObjects

当bean实例化完后立即放入到singletonFactories中,此时其他bean便可引用到该bean了。当singletonFactories中的bean被引用一次后,该bean会被移到earlySingletonObjects中以便提升性能。当bean完全加载完成后,singletonFactories和earlySingletonObjects中bean都会移动到singletonObjects中。三级缓存是互斥的,即任意一个bean不会同时存在于两个缓存中。

以下流程图是bean加载过程中各变量的变化,除已知的变量外,其他变量含义如下:

registeredSingletons:用来保存当前所有已注册的beanName

singletonsCurrentlyInCreation:保存正在创建的beanName,用于构造器循环依赖检测,只要在这个池子里,就表示存在构造器循环依赖,直接抛出异常

add singletonsCurrentlyInCreation
add singletonObjects
add registeredSingletons
remove singletonFactories
remove earlySingletonObjects
实例化
add singletonFactories
add registeredSingletons
remove earlySingletonObjects
再次获取bean(从singletonFactories获取)
首次获取bean
singletonFactory后处理
add earlySingletonObjects
remove 
singletonFactories
remove singletonsCurrentlyInCreation
Object
Object
属性填充init-mehtod
再次获取bean(从earlySingletonObjects获取)
Object

4.2 单例模式的构造器循环依赖

之前说过singletonsCurrentlyInCreation保存正在创建的beanName,如果当前正在创建bean已经存在于该集合中,说明存在循环依赖,直接抛出异常。

下面通过一个例子来说明该问题:A的构造方法需要B,B的构造方法需要A

  • 当创建A时,首先去“singletonsCurrentlyInCreation”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数B,并将A标识符放到“singletonsCurrentlyInCreation”

  • 当创建B时,首先去“singletonsCurrentlyInCreation”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数A,并将B标识符放到“singletonsCurrentlyInCreation”

  • 当再次创建A时,发现该bean标识符在“当前创建bean池”中,表示存在循环依赖,抛出BeanCurrentlyInCreationException

异常的具体代码如下,调用位置是DefaultSingletonBeanRegistry#getSingleton创建单例时抛出的。

DefaultSingletonBeanRegistry.java
1
2
3
4
5
6
7
8
9
10
11
/**
* Callback before singleton creation.
* <p>The default implementation register the singleton as currently in creation.
* @param beanName the name of the singleton about to be created
* @see #isSingletonCurrentlyInCreation
*/
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}

4.3 原型模式的循环依赖

原型模式中,A中有B的属性,B中有A的属性,那么当依赖注入的时候,就会产生当A还未创建完的时候因为对于B的创建再次返回创建A,而Spring不缓存prototype作用域的bean,因此无法提前暴露一个创建中的bean,所以造成循环依赖。

AbstractBeanFactory类中使用prototypesCurrentlyInCreation来保存正在创建的原型名称,而其中有如下一段代码正是解决原型模式的循环依赖问题的,当遇到有原型bean正在创建时,它只是简单的抛出异常。

AbstractBeanFactory.java
1
2
3
4
5
6
7
8
9
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
...
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×