在使用spring的场景中,有时会碰到如下的一种情况,即bean之间的循环引用。即两个bean之间互相进行引用的情况。这时,在spring xml配置文件中,就会出现如下的配置:
1 | <bean id="beanA" class="BeanA" p:beanB-ref="beaB"/> |
并且,在一般情况下,这个配置在现有的spring3.0中是可以正常工作的,前提是没有对beanA和beanB进行增强。但是,如果任意一方进行了增强,比如通过spring的代理对beanA进行了增强,即实际返回的对象和原始对象不一致的情况,在这种情况下,就会报如下一个错误:
1 | "Bean with name '" + beanName + "' has been injected into other beans [" + |
这个错误即对于一个bean,其所引用的对象并不是由spring容器最终生成的对象,而只是一个原始对象,而spring不允许这种情况出现,即持有过程中间对象。那么,这个错误是如何产生的,以及在spring内部,是如何来检测这种情况的呢。这就得从spring如何创建一个对象,以及如何处理bean间引用,以及spring使用何种策略处理循环引用问题说起。
这里会涉及到在spring内部所使用的两个内部属性,singletonFactories和earlySingletonObjects,这两个属性在类DefaultSingletonBeanRegistry中被定义,定义如下:
1 | /** Cache of singleton factories: bean name --> ObjectFactory */ |
官方对此的属性定义不是很明确,这里我们可以这样来理解。
- singletonFactories,用于存储在spring内部所使用的beanName->对象工厂的引用,一旦最终对象被创建(通过objectFactory.getObject()),此引用信息将删除
- earlySingletonObjects,用于存储在创建Bean早期对创建的原始bean的一个引用,注意这里是原始bean,即使用工厂方法或构造方法创建出来的对象,一旦对象最终创建好,此引用信息将删除
从上面的解释,可以看出,这两个对象都是一个临时工。在所有的对象创建完毕之后,此两个对象的size都为0。
那么再来看下这两个对象如何进行协作:
方法1:
1 | /** |
方法2:
1 | /** |
方法3:
1 | /** |
方法1和方法2中的官方注释都很明显地显示了,针对于循环引用的处理,即能够处理循环引用问题。
在方法1中,对象信息对beanFactory的形式被放入singletonFactories中,这时earlySingletonObjects中肯定没有此对象(因为remove)。
在方法2中,在一定条件下(allowEarlyReference为true)的条件下,对象从singleFactories中的objectFactory中被取出来,同时remove掉,被放入earlySingletonObjects中。这时,earlySingletonObjects就持有对象信息了;当然,如果allowEarlyReference为false的情况下,且earlySingletonObjects本身就没有持有对象的情况下,肯定不会将对象从objectFactory中取出来的。这个很重要,因为后面将根据此信息进行循环引用处理。
在方法3中,对象被加入到singletonObjects中,同时singletonFactories和earlySingletonObjects中都remove掉持有的对象(不管持有与否),这就表示在之前的处理中,这只相当于一个临时容器,处理完毕之后都会remove掉。
那么,我们来看这3个方法是不是按照先后顺序被调用的呢。代码顺序如下所示:
类AbstracBeanFactory获取bean。M-1
1 | protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { |
进入getSingleton方法M-2
1 | Object singletonObject = this.singletonObjects.get(beanName); |
查看singletonFactory.getObject(),即createBean(beanName, mbd, args),最终转向doCreateBean方法M-31
2
3
4
5
6
7
8
9
10protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
instanceWrapper = createBeanInstance(beanName, mbd, args);
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
上面代码会调用方法addSingletonFactory,即上文所说的方法1。
那么方法2会在什么地方调用呢。答案在两个地点。
第一个地方,称之为调用点A,即在最开始获取bean时,会调用。
1 | Object sharedInstance = getSingleton(beanName); |
此方法最终会调用到
1 | getSingleton(beanName, true) |
这里传递了参数true。即会尝试解析singletonFactories。然而,在最开始创建对象时,singletonFactories中肯定不会持有对象信息,所以会返回null。
第二个地方,称之为调用点B,即在完成bean创建时,会有一个验证过程。即在方法M-3中,即在调用方法2之前。代码如下:
1 | if (earlySingletonExposure) { |
调用点B的逻辑有点多,后面的逻辑主要是作循环引用验证。注意在调用点B传递参数为false,即不会解析singletonFactories。
在正常的情况下,调用顺序如下:以下有无,表示是否持有对指定Bean的引用
method name | singletonFactories | earlySingletonObjects | singletonObjects |
---|---|---|---|
getSingleton(beanName, true) | 无 | 无 | 无 |
doCreateBean(beanName,mdb,args) | 有 | 无 | 无 |
getSingleton(beanName, true); | 有 | 无 | 无 |
addSingleton(beanName, singletonObject) 无 | 无 | 有 |
但是出现循环引用之后呢,就会出现这种情况:
method name | singletonFactories | earlySingletonObjects | singletonObjects |
---|---|---|---|
getSingleton(A, true); | A无B无 | A无B无 | A无B无 |
doCreateBean(A,mdb,args) | A有B无 | A无B无 | A无B无 |
populateBean(A, mbd, instanceWrapper) 解析B…… | |||
getSingleton(B, true) | A有B无 | A无B无 | A无B无 |
doCreateBean(B,mdb,args) | A有B有 | A无B无 | A无B无 |
populateBean(B, mbd, instanceWrapper)由B准备解析A…… | |||
getSingleton(A, true) | A无B有 | A有B无 | A无B无 |
完成populateBean(B, mbd, instanceWrapper)解析…… | |||
addSingleton(B, singletonObject) | A无B无 | A有B无 | A无B有 |
完成populateBean(A, mbd, instanceWrapper) | |||
A- = initializeBean(beanName, exposedObject, mbd)在initializeBean之后A变为A- | |||
getSingleton(A, false);验证 | |||
addSingleton(A, singletonObject) | …… |
在上面这个过程中,在对A进行验证时,就会从earlySingletonObjects中取得一个A,但是这个A和后面的A-可能不是同一个对象,这是因为有了beanPostProcessor存在,它可以改变bean的最终值,比如对原始bean进行封装,代理等。在这个过程中,出现了3个对象A,A-,B,而B中所持有的A对象为原始的A。如果这里的A和A-不是同一个对象,即产生了beanA有了beanB的引用,但beanB并没有beanA的引用,而是另一个beanA的引用。这肯定不满足条件。
那么我们来看spring对这种情况的处理,即在上文中的方法3,再次将代码贴在下面:
1 | Object earlySingletonReference = getSingleton(beanName, false); |
上面有4个判断点,依次如下
判断点1,首先确定这个对象能从earlySingletonObjects中取出对象来,经过上面的分析,我们知道,在正常情况下,此对象为null,即不存在循环检测。而在循环引用中,此对象能够被取出来。
判断点2,再判断这个对象和当前通过beanPostProcessor处理过的对象是否相同,如果相同,表示对象没有经过修改,即A=A-,那么循环引用成立。无需处理
判断点3,判断当前对象A是否被其他对象所依赖,在循环引用中,已经处理了A和B,那么在依赖表中,即在属性dependentBeanMap和dependenciesForBeanMap中。其中A->B表示A依赖于B,B->A表示B依赖于A。那么在dependentBeanMap中就会出现两个entry,分别为A->B和B->A。这里A依赖于A,那么表示A已经被依赖,则进入进一步检测中。在检测中,将取得一个A的被依赖列表中的bean已经被创建的对象列表值。
判断点4,如果被依赖对象列表不为空,则表示出现循环引用。因为按照创建规则,如果A->B,则必须先创建B,而B->A,则必须先创建A。在这里,A被B依赖,就要求A必须在B之前被创建,而B又被A依赖,又要求A必须在B之前被创建。这创建的两个对象必须满足一致才可以。即在A->B中的两个对象,必须和B->A的两个对象,互相一致才可以,否则就不是循环引用。
至此,整个流程梳理清楚。那么,如何处理这种循环引用呢?答案其实也很简单,在xml中将两方的循环切掉。然后使用一个beanPostProcessor即可以,此beanPostProcessor必须要在放到所有beanPostPrcessor的最后面。然后此beanPostProcessor,这样写即可:
1 | 判断当前bean为beanA |