一、什么是循环依赖
当A拥有B属性,B拥有C属性,C拥有A属性,这时就会存在循环依赖
二、先说结论
单例作用域的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
,充分使用了三级缓存。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17protected 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,用于构造器循环依赖检测,只要在这个池子里,就表示存在构造器循环依赖,直接抛出异常
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创建单例时抛出的。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正在创建时,它只是简单的抛出异常。1
2
3
4
5
6
7
8
9protected <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);
}
}