转自:https://www.iflym.com/index.php/code/201208280001.html
在使用spring的场景中,有时会碰到如下的一种情况,即bean之间的循环引用。即两个bean之间互相进行引用的情况。这时,在spring xml配置文件中,就会出现如下的配置:
1
2
<
bean
id
=
"beanA"
class
=
"BeanA"
p:beanB-ref
=
"beaB"
/>
<
bean
id
=
"beanB"
class
=
"BeanB"
p:beanA-ref
=
"beaA"
/>
并且,在一般情况下,这个配置在现有的spring3.0中是可以正常工作的,前提是没有对beanA和beanB进行增强。但是,如果任意一方进行了增强,比如通过spring的代理对beanA进行了增强,即实际返回的对象和原始对象不一致的情况,在这种情况下,就会报如下一个错误:
1
2
3
4
5
6
"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,其所引用的对象并不是由spring容器最终生成的对象,而只是一个原始对象,而spring不允许这种情况出现,即持有过程中间对象。那么,这个错误是如何产生的,以及在spring内部,是如何来检测这种情况的呢。这就得从spring如何创建一个对象,以及如何处理bean间引用,以及spring使用何种策略处理循环引用问题说起。
这里会涉及到在spring内部所使用的两个内部属性,singletonFactories和earlySingletonObjects,这两个属性在类DefaultSingletonBeanRegistry中被定义,定义如下:
1
2
3
4
5
/** Cache of singleton factories: bean name --> ObjectFactory */
private
final
Map singletonFactories =
new
HashMap();
/** Cache of early singleton objects: bean name --> bean instance */
private
final
Map earlySingletonObjects =
new
HashMap();
官方对此的属性定义不是很明确,这里我们可以这样来理解。
singletonFactories,用于存储在spring内部所使用的beanName->对象工厂的引用,一旦最终对象被创建(通过objectFactory.getObject()),此引用信息将删除
earlySingletonObjects,用于存储在创建Bean早期对创建的原始bean的一个引用,注意这里是原始bean,即使用工厂方法或构造方法创建出来的对象,一旦对象最终创建好,此引用信息将删除
从上面的解释,可以看出,这两个对象都是一个临时工。在所有的对象创建完毕之后,此两个对象的size都为0。
那么再来看下这两个对象如何进行协作:
方法1:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/**
* Add the given singleton factory for building the specified singleton
* if necessary.
* 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);
}
}
}
方法2:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Return the (raw) singleton object registered under the given name.
* 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 null
if none found
*/
protected
Object getSingleton(String beanName,
boolean
allowEarlyReference) {
Object singletonObject =
this
.singletonObjects.get(beanName);
if
(singletonObject ==
null
) {
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 != NULL_OBJECT ? singletonObject :
null
);
}
方法3:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
* Add the given singleton object to the singleton cache of this factory.
* 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 !=
null
? singletonObject : NULL_OBJECT));
this
.singletonFactories.remove(beanName);
this
.earlySingletonObjects.remove(beanName);
this
.registeredSingletons.add(beanName);
}
}
方法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
2
3
4
5
6
7
8
protected
T doGetBean(
final
String name,
final
Class requiredType,
final
Object[] args,
boolean
typeCheckOnly)
throws
BeansException {
if
(mbd.isSingleton()) {
sharedInstance = getSingleton(beanName,
new
ObjectFactory() {
public
Object getObject()
throws
BeansException {
try
{
return
createBean(beanName, mbd, args);
} ……
}
进入getSingleton方法M-2
1
2
3
4
5
6
7
8
9
Object singletonObject =
this
.singletonObjects.get(beanName);
try
{
//首先执行getObject方法,再执行finnaly中的addSingleton方法,即上文中的方法3
singletonObject = singletonFactory.getObject();
}
finally
{
addSingleton(beanName, singletonObject);
}
return
(singletonObject != NULL_OBJECT ? singletonObject :
null
);
}
查看singletonFactory.getObject(),即createBean(beanName, mbd, args),最终转向doCreateBean方法M-3
01
02
03
04
05
06
07
08
09
10
protected
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之前。代码如下:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
if
(earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName,
false
);
if
(earlySingletonReference !=
null
) {
if
(exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else
if
(!
this
.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set actualDependentBeans =
new
LinkedHashSet(dependentBeans.length);
for
(String dependentBean : dependentBeans) {
if
(!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if
(!actualDependentBeans.isEmpty()) {
throw
文章开头的异常。
}
}
}
调用点B的逻辑有点多,后面的逻辑主要是作循环引用验证。注意在调用点B传递参数为false,即不会解析singletonFactories。
在正常的情况下,调用顺序如下:以下有无,表示是否持有对指定Bean的引用
singletonFactories
earlySingletonObjects
singletonObjects
getSingleton(beanName, true)
无
无
无
doCreateBean(beanName,mdb,args)
有
无
无
getSingleton(beanName, true);
有
无
无
addSingleton(beanName, singletonObject)
无
无
有
但是出现循环引用之后呢,就会出现这种情况:
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,再次将代码贴在下面:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
Object earlySingletonReference = getSingleton(beanName,
false
);
if
(earlySingletonReference !=
null
) {
//判断点1
if
(exposedObject == bean) {
//判断点2
exposedObject = earlySingletonReference;
}
else
if
(!
this
.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {判断点
3
String[] dependentBeans = getDependentBeans(beanName);
Set actualDependentBeans =
new
LinkedHashSet(dependentBeans.length);
for
(String dependentBean : dependentBeans) {
if
(!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if
(!actualDependentBeans.isEmpty()) {判断点
4
抛出对象不致异常。
}
上面有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
2
3
4
判断当前bean为beanA
BeanB beanB=beanFactory.getBean(“beanB”);
beanA.setBeanB(beanB);
beanB.setBeanA(beanA);