Spring源码之Bean循环依赖问题
本文尝试回答以下几个问题
- Spring如何表示在Bean创建过程当中Bean与Bean之间的依赖关系以及这些依赖关系是何时建立的
- Spring如何检测循环依赖
- 哪些情况下的循环依赖能够解决,哪些不能,Spring是如何解决的
在阅读本文前,熟悉BeanFactory体系,Bean的创建流程和各种回调接口的触发时机,有助于理解本文。
Bean之间的依赖关系
如果某个类A在运行时需要B的协助,则可以在A中声明一个B类型的成员变量,此时就可以看做A依赖B。如果A和B都作为Bean被Spring容器管理,那么在容器启动时需要自动将B注入给A。Spring容器支持的依赖注入方式有:构造器注入、属性注入、工厂方法注入(静态工厂或实例工厂)。
Spring中单例Bean的注册、注销和管理都是DefaultSingletonBeanRegistry实现的。首先我们看一看这个它是如何管理Bean的依赖关系的。
/** /** Map between dependent bean names: bean name to Set of dependent bean names. */
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
/** Map between depending bean names:
bean name to Set of bean names for the bean's dependencies. */
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);
dependentBeanMap代表一个Bean和依赖它的Bean之间的映射关系,表示某个Bean被哪些Bean依赖
dependenciesForBeanMap代表 从一个Bean 到 它依赖的Bean之间的映射关系,表示某个Bean依赖哪些Bean
举个例子:如果A依赖B ,B依赖C和D,C也依赖D,D又依赖A,则两个Map中的元素如下:
dependentBeanMap:
A-->{D}
B-->{A}
C-->{B}
D-->{B,C}
dependenciesForBeanMap:
A-->{B}
B-->{C,D}
C-->{D}
D-->{A}
Bean之间的这种依赖关系何时建立?通过什么方式建立?DefaultSingletonBeanRegistry通过registerDependentBean方法建立Bean之间的依赖关系。
/**
* Register a dependent bean for the given bean,
* to be destroyed before the given bean is destroyed.
* @param beanName the name of the bean
* @param dependentBeanName the name of the dependent bean
* 第一个参数:被依赖的Bean的名字
* 第二个参数:依赖第一个参数的Bean的名字
*/
public void registerDependentBean(String beanName, String dependentBeanName) {
String canonicalName = canonicalName(beanName);
synchronized (this.dependentBeanMap) {
Set<String> dependentBeans =
this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
if (!dependentBeans.add(dependentBeanName)) {
return;
}
}
synchronized (this.dependenciesForBeanMap) {
Set<String> dependenciesForBean =
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
dependenciesForBean.add(canonicalName);
}
}
registerDependentBean方法何时被调用呢?
| 依赖声明方式 | 依赖关系注册时机 |
|---|---|
| 构造器注入 | Spring在创建Bean实例阶段,会对构造器参数进行解析。在解析构造方法参数时,创建和加载依赖bean成功后,注册依赖关系 |
| 属性注入 | 在填充Bean属性阶段,在解析Bean属性值时,获取依赖bean成功后,注册依赖关系 |
| 实例工厂方法注入 | 不注册依赖关系 |
| depend-on | 在当前Bean创建之前,加载和创建其依赖的Bean之前,注册依赖关系 |
| autowireByType或autowireByName | 在填充Bean属性阶段,在解析Bean属性值时,加载和创建依赖的bean成功后,注册依赖关系 |
至此,我们已经清楚Bean依赖关系的建立方式和时机。那么Spring是如何检测循环依赖的呢?
循环依赖的检测
循环依赖的几种形式
这里我们不再解释有关循环依赖的概念。但我们要清楚,循环依赖有哪几种表现形式,Spring能够处理的循环依赖需要满足哪些条件:
- 所有构造方法造成的循环依赖Spring是不能处理的,但是能检测出来并给出错误提示。
- 所有Prototype作用域的Bean之间的循环依赖Spring是不能处理的,同样只能检测并给出错误提示。
- Spring只能处理单例Bean的属性注入形式造成的循环依赖,且发生循环依赖时单例Bean不能被代理增强。
示例:
public class Door {
private Lock lock;
public Door() {
}
public Door(Lock lock) {
this.lock = lock;
}
public Lock getLock() {
return lock;
}
public void setLock(Lock lock) {
this.lock = lock;
}
}
public class Lock {
private Door door;
public Lock() {
}
public Lock(Door door) {
this.door = door;
}
public Door getDoor() {
return door;
}
public void setDoor(Door door) {
this.door = door;
}
}
使用构造方法注入方式造成循环依赖,Spring无法处理,只能给出错误提示:
<bean id="lock" class="com.lspring.beans.Lock">
<constructor-arg ref="door"/>
</bean>
<bean id="door" class="com.lspring.beans.Door">
<constructor-arg ref="lock"/>
</bean>
Prototype作用域的Bean之间的循环依赖Spring是不能处理的,同样只能检测并给出错误提示:
<bean id="lock" class="com.lspring.beans.Lock" scope="prototype">
<property name="door" ref="door"/>
</bean>
<bean id="door" class="com.lspring.beans.Door" scope="prototype">
<property name="lock" ref="lock"/>
</bean>
单例Bean的属性注入方式导致的循环依赖,Spring可以处理,如:
<bean id="lock" class="com.lspring.beans.Lock" >
<property name="door" ref="door"/>
</bean>
<bean id="door" class="com.lspring.beans.Door" >
<property name="lock" ref="lock"/>
</bean>
循环依赖的检测方式
如果发生循环依赖且Spring不能自动处理,则会报BeanCurrentlyInCreationException异常:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'lock': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:264)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:303)
... 16 more
想用弄清楚Spring是如何检测循环依赖的,我们必须清楚Spring加载和创建Bean的整个流程。单例Bean的加载和创建流程如下:
Bean加载流程

转化为逻辑流程图如下:

在整个Bean的加载过程中,Spring是在哪一步对单例的循环依赖做检测的呢?答案其实很简单,就在beforeSingletonCreation方法中:
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
这一步需要将Bean标记为正在创建状态,其实就是将Bean名称加入到singletonsCurrentlyInCreation这个set集合当中。如果加入失败,说明当前即将创建的Bean已经是正在创建状态了,此时一定发生了循环依赖,直接抛异常。这里读者在阅读源码的时候先不用纠结!this.inCreationCheckExclusions.contains(beanName)这个判断,这是对singletonsCurrentlyInCreation的一种屏蔽机制。Spring在创建过程中对于那些已经处于正在创建状态的Bean可以屏蔽其状态。
对于Prototype的Bean的循环依赖,其检测方式与单例Bean类似:
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
读者可自行查看源码。
解决单例循环依赖
Spring是如何解决单例Bean在属性注入时的循环依赖问题的呢?答案就隐藏在将Bean提早曝光到三级缓存这一步骤中,将Bean提早曝光后,其他依赖这个Bean的Bean在创建时就可以提前从三级缓存中加载到这个Bean了,而当前Bean的创建流程则继续。其实就是把一个半成品的bean给容器,让其他bean先用着。我们首先来看这个三级缓存的结构:
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
第一级缓存singletonObjects:存放已经完全创建好的Bean,这里的完全创建好,指的是走完整个Bean的创建流程,实例化、属性填充和初始化三个阶段。
第二级缓存earlySingletonObjects:存放通过singletonFactories中的ObjectFactory的getObject()方法拿到的提早曝光的Bean.这里面存放的Bean并不是被Spring完全加工好的,只是经历了SpringBean创建流程的第一个阶段,即初始化阶段,并没有经历属性填充和初始化。
第三级缓存singletonFactories:存放能够得到早期曝光Bean的ObjectFactory。通过ObjectFactory的getObject()方法能够拿到早期曝光的Bean实例。
首先看下关于这三个缓存的三个操作方法:
/**
* Add the given singleton object to the singleton cache of this factory.
* <p>To be called for eager registration of singletons.
* @param beanName the name of the bean
* @param singletonObject the singleton object
*/
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);
}
}
Spring只会在Bean创建完成之后才会调用addSingleton将Bean添加到单例池中,一旦Bean被添加到单例池中,说明Bean已经被完全创建完成,earlySingletonObjects和singletonFactories中的信息立即失效,被移除。
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
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时,先尝试从singletonObjects单例池中获取,如果没有,则从earlySingletonObjects获取早期曝光的Bean,如果还是没有,则allowEarlyReference参数判断,从singletonFactories中拿到可以获取早期Bean的ObjectFactory,执行getObject方法。注意,getObject方法一旦执行,获取的早期Bean被放入earlySingletonObjects中,而ObjectFactory立即失效,从singletonFactories中移除。具体getObject执行的是什么逻辑?寻着代码,找到只有addSingletonFactory会向singletonFactories添加ObjectFactory对象
/**
* Add the given singleton factory for building the specified singleton
* if necessary.
* <p>To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
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);
}
}
}
而调用addSingletonFactory的代码只有一处:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
在doCreateBean方法中,Spring将Bean实例化好之后,立即就通过该方法将Bean暴露出去。请思考:问什么不将Bean直接放到earlySingletonObjects中去,而提供一个ObjectFactory放到singletonFactories呢,在获取Bean的时候再调用getObject方法取呢?这是Spring用三级缓存,而不是用二级缓存解决循环依赖的关键。
分析完以上几个方法的逻辑,再回头看看上文中的Bean加载的逻辑流程图,我们分析下这三个操作缓存的方法的调用时机。
- 首先,Spring在获取Bean时,先使用getSingleton(beanName,true)从缓存中加载,true表示三级缓存都会被检查。
- 如果没有加载到,则着手创建bean,不过在真正创建bean之前,先将bean标记为正在创建状态。
- 开始创建Bean,首先对Bean进行实例化(通过合适的实例化策略),这里简单理解为通过构造方法创建对象,即: A a = new A()。
- bean实例化后,通过addSingletonFactory对其进行曝光(注意这时bean创建流程还没有走完,只走了第一步)。
- 接下来走完bean的属性填充和初始化这两个阶段,bean的创建工作其实已经完成了。
- 走完创建流程后,关键的一步来了,Spring要检查早期曝光的Bean是否被其他Bean使用。通过getSingleton(beanName, false) Spring就知道earlySingletonObjects中有没有bean,如果有,则早期曝光的Bean肯定被使用了,这时候当前bean还没有创建完成,一个没有创建完成的bean被别的bean使用,那就存在循环依赖了(理解这一步,是理解Spring用三级缓存而不是用两级缓存的关键,Spring通过将早期Bean包装成ObjectFactory再暴露,而不是直接将bean直接暴露到earlySingletonObjects,从而能够感知到在当前bean创建过程中,有没有bean依赖当前bean)。这时候Spring就要检查经过属性填充和初始化这两个阶段后的bean,跟早期暴露出去的Bean还是不是同一个对象?要知道初始化阶段的BeanPostProcessor可能会对Bean进行包装,代理增强操作的。我们看看检查逻辑:
if (earlySingletonExposure) {
/**
*
首先调用getSingleton(beanName, false)从缓存中拿bean实例,注意这里的false参数,
表明只会从singletonObjects和earlySingletonObjects这两级缓存中拿。
如果没拿到,很好,说明当前Bean的创建过程中没有别的Bean依赖当前Bean,也就不存在循环依赖的可能性。
*/
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
/**
* 拿到了,说明当前Bean创建的过程中,有别的Bean创建时依赖了当前Bean,并拿到了早期曝光的当前Bean。
* 这种情况下需要检查当前initializeBean返回的bean和 getEarlyBeanReference返回的是不是同一个对象
* 这里 bean 是实例化出来的bean
* earlySingletonReference 是早期被暴露出去的getEarlyBeanReference返回的bean
* exposedObject是initializeBean方法返回的bean
* 比较exposedObject与bean是不是同一个对象就能知道bean有没有被initializeBean增强
* 如果没有被增强,则设置exposedObject = earlySingletonReference earlySingletonReference是别的Bean进行属性注入时,拿到的当前bean对象
* exposedObject是要被放入Spring容器的,因此这两个对象要一致才行
* 而earlySingletonReference有可能被getEarlyBeanReference方法增强
*/
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
/**
* 这里说明当前bean在初始化阶段被增强了(被initializeBean增强),依赖当前Bean的Bean拿到的对象跟马上要被放入到容器中的对象
* 不是同一个对象了
* 这里调用getDependentBeans拿到那些依赖当前Bean的Bean
* 检查这些Bean是不是 仅仅是为了类型检查而创建的,
* 如果全都是为了类型检查而创建的 这些bean不会被放入容器中,也就不造成不一致
* 如果有不是为了类型检查而创建的bean,则会造成不一致,要抛异常
*/
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!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 " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
- 进行将bean的正在创建状态取消,实例处理,将bean加入单例缓冲池等等后续工作。
我们拿个例子来看一下Spring如何解决循环依赖的:
<bean id="lock" class="com.lspring.beans.Lock" >
<property name="door" ref="door"/>
</bean>
<bean id="door" class="com.lspring.beans.Door" >
<property name="lock" ref="lock"/>
</bean>
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
String path = System.getProperty("app-bean-path");
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(path);
Object lock = factory.getBean("lock");
| 创建Lock | 创建Door | 缓存状态 |
|---|---|---|
| 从缓存中获取lock-->失败getSingleton("lock",true) | singletonObjects:无 earlySingletonObjects:无 singletonFactories:无 | |
| 创建Bean实例createBeanInstance | ||
| 将lock提早曝光addSingletonFactory | singletonObjects:无 earlySingletonObjects:无singletonFactories:lock | |
| 开始填充bean属性 populateBean start | ||
| 从缓存中获取doorgetSingleton("door",true) | ||
| 创建Bean实例createBeanInstance | ||
| 将door提早曝光addSingletonFactory | singletonObjects:无earlySingletonObjects:无singletonFactories:lock,door | |
| 填充door的属性开始populateBean start | ||
| 从缓存中获取lock-->成功getSingleton("lock",true) | singletonObjects:无earlySingletonObjects:lock singletonFactories:door | |
| 填充door的属性结束populateBean end | ||
| 初始化doorinitializeBean | ||
| 检查早期曝光Bean(door)是否被使用(未使用)check earlySingletonExposure | ||
| 将Bean加入到单例池中 addSingleton | singletonObjects:door earlySingletonObjects:lock singletonFactories:无 | |
| 填充bean属性 结束populateBean end | ||
| 初始化Bean initializeBean | ||
| 检查早期曝光Bean(lock)是否被使用(被使用,检查lock是否被增强(未被增强),检验合格)check earlySingletonExposure | ||
| 将Bean加入到单例池中addSingleton | singletonObjects:door,lockearlySingletonObjects:无singletonFactories:无 |
读完本文,请尝试回答以下问题:
- Spring如何检测循环依赖?
- 对于属性注入的循环依赖,明明也是循环依赖的一种,为什么不会抛出BeanCurrentlyInCreationException?
- Spring如何解决属性注入方式的循环依赖?bean是在创建的哪个阶段被提早曝光的?
- Spring为什么使用三级缓存来解决循环依赖?二级缓存不行吗?你能想到其他访问来解决循环依赖吗?
这里关键要考虑清楚 earlySingletonObjects和singletonFactories的作用。
singletonFactories存放的是getEarlyBeanReference方法,如果有别的bean循环依赖了当前bean,该方法才会被执行,getEarlyBeanReference的作用就是将动态代理的创建从initializeBean阶段提前到populateBean之前,
以保证即使是当前bean进行了增强,那么依赖当前bean的bean也能拿到正确的代理实例。
earlySingletonObjects存放的是singletonFactories执行的结果。为什么要设置这个?为什么不直接放到singletonObjects?earlySingletonObjects是Spring检测当前bean是否被循环依赖的关键。
注意Spring在doCreateBean方法的最后阶段,也就是bean的整个创建流程执行完之后,通过getSingleton(beanName, false)又获取一次当前bean,该方法只能从singletonObjects和earlySingletonObjects中拿bean,如果该方法返回null,说明 singletonFactories中的方法没有被执行,也就是说当前bean肯定不存在循环依赖。如果没有返回null,spring即感知到当前bean的创建过程中有循环依赖。 - 请自己画出prototype bean的加载流程,并解释为什么prototype bean的循环依赖不能解决。
以下文章对本文帮助很大:
浙公网安备 33010602011771号