前言
在工作中,经常由于设计不佳或各种原因,导致类之间相互依赖。这些类可能单独使用时不会出现问题,但是在使用Spring进行管理的时候可能就会抛出BeanCurrentlyInCreationException等异常。当抛出这种异常时表示Spring解决不了该循环依赖。本文将说明Spring对于循环依赖的解决方法。
我在原作者的文章上略做修改,也可以去看原文。
简介
本文,我们来看一下Spring是如何解决循环依赖问题的。在本篇文章中,我会首先向大家介绍以下什么是循环依赖。然后进入源码分析阶段。为了更好的说明Spring解决循环依赖的办法,我将会从获取bean的方法getBean(String)开始,把整个调用过程梳理一遍。梳理完后,再来详细分析源码。通过这几步讲解,希望让大家能够弄懂什么是循环依赖,以及如何解决循环依赖。
文章内容较长,有点耐心看完。
循环依赖的产生和解决的前提
循环依赖的产生可能有很多种情况,例如:
- A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象。
- Ade构造方法中依赖了B的实例对象,同时B的某个field或者setter需要A的实例对象,以及反之。
- A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象,以及反之。
当然,Spring对于循环依赖的解决不是无条件的,首先前提条件是针对scope单例并且没有显示指明不需要解决循环依赖的对象,而且要求该对象没有被代理过。同时Spring解决循环依赖也不是万能,以上三种情况只能解决两种,第一种在构造方法中相互依赖的情况Spring也无力回天。(知道了以下解决方案就明白为什么第一种情况无法解决了)。
背景知识
什么是循环依赖
所谓的循环依赖是指 A依赖B,B又依赖A,它们之间形成了循环依赖。或者是 A依赖B,B依赖C,C又依赖A。
这里以两个类直接相互依赖为例,它们的实现代码如下:
12345678 | public class { private BeanA beanA; }public class BeanA { private BeanB beanB;} |
配置信息如下:
123456 | <bean id="beanA" class="xyz.coolblog.BeanA"> <property name="beanB" ref="beanB"/></bean><bean id="beanB" class="xyz.coolblog.BeanB"> <property name="beanA" ref="beanA"/></bean> |
IOC容器在读到上面的配置时,会按照顺序,先去实例化beanA。然后发现beanA依赖于beanB,接着又去实例化beanB。实例化beanB时,发现beanB又依赖于beanA。如果容器不处理循环依赖的话,容器会无限执行上面的流程,直到内存溢出,程序崩溃。
当然,Spring是不会让这种情况发生的。在容器再次发现beanB依赖于beanA时,容器会获取beanA对象的一个早期引用(early reference),并把这个早期引用注入到beanB中,让beanB先完成实例化。beanB完成实例化,beanA就可以获取到beanB的引用,beanA随之完成实例化。
早期引用这个概念在接下来会讲。
一些缓存的介绍
在进行源码分析前,先来看一组缓存的定义。
| 缓存 | 用途 |
|---|---|
| singletonObjects | 用于存放完全初始化好的bean,从该缓存中取出的bean可以直接使用。 |
| earlySingletonObjects | 存放原始的bean对象(尚未填充属性),用于解决循环依赖。 |
| singletonFactories | 存放bean工厂对象,用于解决循环依赖。 |
- 之前提到了早期引用,所谓的早期引用是指向原始对象的引用。所谓的原始对象是指刚创建好的对象,但还未填充属性。
12345678 | /** Room 包含了一些电器 */public class Room { private String television; private String airConditioner; private String refrigerator; private String washer; } |
配置:
123456 | <bean id="room" class="xyz.coolblog.demo.Room"> <property name="television" value="Xiaomi"/> <property name="airConditioner" value="Gree"/> <property name="refrigerator" value="Haier"/> <property name="washer" value="Siemens"/></bean> |
我们先看一下完全实例化好后的bean长什么样:
从调试信息中可以看得出,Room的每个成员变量都被赋值了。然后再看一下原始的bean对象长什么样:
结果就比较明显了,所有字段都是 null。这里的bean和上面的bean指向的是同一个对象Room@1567,但现在这个对象所有字段都是null,我们把这种对象称为原始的对象。
回顾获取bean的过程
本节,我们来了解从Spring IOC容器中获取bean实例的流程(简化版)。
先来简单介绍一下这张图,这图是一个简化后的流程图。开始流程图中只有一条执行路径,在条件sharedInstance != null这里出现了岔路,形成了绿色和红色两条路径。在上图中,读取/添加缓存的方法使用蓝色狂和☆标注了出来。
- 这个流程从
getBean方法开始,getBean是一个空壳方法,所有逻辑都在doGetBean方法中。 doGetBean首先会调用getSingleton(beanName)方法获取sharedInstance,sharedInstance可能是完全实例化好的bean,也可能是一个原始的bean,当然也有可能是 null。- 如果不是null,则走绿色的那条路径。再经
getObjectForBeanInstance这一步处理后,绿色这条路径就结束了。 - 如果是null,则走红色那条路径。在第一次获取某个bean的时候,缓存中是没有记录的,所以这个时候要走创建逻辑。上图中的
getSingleton(beanName,new ObjectFactory(){...})方法会创建一个bean实例,上图虚线路径指的是getSingleton方法内部调用的两个方法,其逻辑如下:
123456 | public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { // 省略部分代码 singletonObject = singletonFactory.getObject(); // ... addSingleton(beanName, singletonObject);} |
如上所示,
getSingleton会在内部先调用getObject方法创建singletonObject。- 然后再调用
addSingleton将singletonObject放入缓存中。 getObject在内部调用了createBean方法,createBean方法基本上也属于空壳方法,更多的逻辑是写在doCreateBean方法中。doCreateBean方法中的逻辑很多,其首先调用了createBeanInstance方法创建一个原始的bean对象。- 随后调用
addSingletonFactory方法向缓存中添加单例bean工厂,从该工厂可以获取原始对象的引用,也就是所谓的早期引用。 - 再之后,继续调用
populateBean方法向原始bean对象中填充属性,并解析依赖。 getObject执行完成后,会返回完全实例化好的bean。紧跟着再调用addSingleton把完全实例化好的bean对象放入缓存中。- 到这里,红色执行路径差不多也就要结束。
这里没有把getObject、addSingleton方法和getSingleton(String,ObjectFactory)并列画在红色的路径里,目的是想简化一下方法的调用栈。可以进一步简化上面的调用流程:
这里我贴出几个源码:
源码分析
经过前面的铺垫,现在终于可以深入源码一探究竟了。依次来看一下循环依赖相关的代码。如下:(通过上面截图,也标注出了类名,有兴趣可以点进去看,都一样,只是以下代码原博主加上了注释,讲的很详细。)
1234567891011121314151617181920212223242526272829303132333435363738394041 | protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { // ...... // 从缓存中获取 bean 实例 Object sharedInstance = getSingleton(beanName); // ......}//getSingleton(beanName)方法与doGetBean方法并不在同一个类中。public Object getSingleton(String beanName) { return getSingleton(beanName, true);}protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 从 singletonObjects 获取实例,singletonObjects 中的实例都是准备好的 bean 实例,可以直接使用 Object singletonObject = this.singletonObjects.get(beanName); // 判断 beanName 对应的 bean 是否正在创建中 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 从 earlySingletonObjects 中获取提前曝光的 bean singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 获取相应的 bean 工厂 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 提前曝光 bean 实例(raw bean),用于解决循环依赖 singletonObject = singletonFactory.getObject(); // 将 singletonObject 放入缓存中,并将 singletonFactory 从缓存中移除 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null);} |
上面的源码中,doGetBean所调用的方法getSingleton(String)是一个空壳方法,其主要逻辑在getSingleton(String,boolean)中。该方法逻辑比较简单:
- 首先从
singletonObjects缓存中获取bean实例。 - 若未命中,再去
earlySingletonObjects缓存中获取原始bean实例。 - 如果仍未命中,则从
singletonFactory缓存中获取ObjectFactory对象。 - 然后再调用
getObject方法获取原始bean实例的应用,也就是早期引用。 - 获取成功后,将该实例放入
earlySingletonObjects缓存中,并将ObjectFactory对象从singletonFactories移除。
看完这个方法,再来看看getSingleton(String,ObjectFactory)方法,这个方法也是在doGetBean中被调用的。这次会把doGetBean的代码多贴出来点,如下:(源码里doGetBean这个方法有点太长了,但是确实又调用了getSingleton(String,ObjectFactory)方法,好好找。)
1234567891011121314151617181920212223242526272829303132333435363738394041424344 | protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { // ...... Object bean; // 从缓存中获取 bean 实例 Object sharedInstance = getSingleton(beanName); // 这里先忽略 args == null 这个条件 if (sharedInstance != null && args == null) { // 进行后续的处理 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // ...... // mbd.isSingleton() 用于判断 bean 是否是单例模式 if (mbd.isSingleton()) { // 再次获取 bean 实例 sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() { public Object getObject() throws BeansException { try { // 创建 bean 实例,createBean 返回的 bean 是完全实例化好的 return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } } }); // 进行后续的处理 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // ...... } // ...... // 返回 bean return (T) bean;} |
这里的代码逻辑和在上面所说的回顾获取bean的过程一节的最后贴的主流程图已经很接近了,对照那张图和代码中的注释,可以理解doGetBean方法了。继续看:
1234567891011121314151617181920212223242526272829303132 | public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { // ...... // 调用 getObject 方法创建 bean 实例 singletonObject = singletonFactory.getObject(); newSingleton = true; if (newSingleton) { // 添加 bean 到 singletonObjects 缓存中,并从其他集合中将 bean 相关记录移除 addSingleton(beanName, singletonObject); } // ...... // 返回 singletonObject return (singletonObject != NULL_OBJECT ? singletonObject : null); }}protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { // 将 <beanName, singletonObject> 映射存入 singletonObjects 中 this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT)); // 从其他缓存中移除 beanName 相关映射 this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); }} |
上面的代码中包含两步操作:
- 第一步操作是调用
getObject创建bean实例。 - 第二步是调用
addSingleton方法将创建好的bean放入缓存中。代码逻辑并不复杂。
接下来继续看,这次分析的是doCreateBean中的一些逻辑。(这个方法上面说过了,是在getSingelton(String,ObjectFactory)的getObject
方法中内部调用了createBean之后createBean又调用了doCreateBean。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162 | protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; // ...... // ☆ 创建 bean 对象,并将 bean 对象包裹在 BeanWrapper 对象中返回 instanceWrapper = createBeanInstance(beanName, mbd, args); // 从 BeanWrapper 对象中获取 bean 对象,这里的 bean 指向的是一个原始的对象 final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); /* * earlySingletonExposure 用于表示是否”提前暴露“原始对象的引用,用于解决循环依赖。 * 对于单例 bean,该变量一般为 true。更详细的解释可以参考我之前的文章 */ boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // ☆ 添加 bean 工厂对象到 singletonFactories 缓存中 addSingletonFactory(beanName, new ObjectFactory<Object>() { public Object getObject() throws BeansException { /* * 获取原始对象的早期引用,在 getEarlyBeanReference 方法中,会执行 AOP * 相关逻辑。若 bean 未被 AOP 拦截,getEarlyBeanReference 原样返回 * bean,所以大家可以把 * return getEarlyBeanReference(beanName, mbd, bean) * 等价于: * return bean; */ return getEarlyBeanReference(beanName, mbd, bean); } }); } Object exposedObject = bean; // ...... // ☆ 填充属性,解析依赖 populateBean(beanName, mbd, instanceWrapper); // ...... // 返回 bean 实例 return exposedObject;}protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 将 singletonFactory 添加到 singletonFactories 缓存中 this.singletonFactories.put(beanName, singletonFactory); // 从其他缓存中移除相关记录,即使没有 this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }} |
上面的代码简化了不少,不过看起来仍有点复杂。好在,上面的代码主线逻辑比较简单,由三个方法组成。如下:
- 创建原始bean实例 →
createBeanInstance(beanName,mbd,args)。 - 添加原始对象工厂对象到
singletonFactories缓存中。→addSingletonFactory(beanName,new ObjectFactory<Object>{...}) - 填充属性,解析依赖 →
populateBean(beanName,mbd,instanceWrapper)
到这里,本节涉及到的源码就解析完了。可是看完源码后,似乎仍然不知道这些源码是如何解决循环依赖问题的。下面来解答这个问题,这里还是以BeanA和BeanB两个类相互依赖为例。在上面的方法调用中,有几个关键的地方,下面一一列举出来:
- 创建原始bean对象
12 | instanceWrapper = createBeanInstance(beanName, mbd, args);final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); |
假设beanA先被创建,创建后的原始对象为BeanA@1234,上面代码中的bean变量指向就是这个对象。
- 暴露早期引用
123456 | addSingletonFactory(beanName, new ObjectFactory<Object>() { public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); }}); |
beanA指向的原始对象创建好后,就开始把指向原始对象的引用通过ObjectFactory暴露出去。getEarlyBeanReference方法的第三个参数bean指向的正是createBeanInstance方法创建出原始对象BeanA@1234。
- 解析依赖
1 | populateBean(beanName, mbd, instanceWrapper); |
populateBean用于向beanA这个原始对象中填充属性,当它检测到beanA依赖于beanB时,会首先去实例化beanB。beanB在此方法处也会解析自己的依赖,当它检测到beanA这个依赖,于是调用BeanFactory.getBean("beanA")这个方法,从容器中获取beanA。
- 获取早期引用
1234567891011121314151617181920 | protected Object getSingleton(String beanName, boolean allowEarlyReference) { |