转自: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<String, ObjectFactory> singletonFactories = new HashMap<String, ObjectFactory>(); /** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(); |
官方对此的属性定义不是很明确,这里我们可以这样来理解。
- 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. * <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); } } } |
方法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. * <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</code> 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. * <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 != 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> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() { 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<String> actualDependentBeans = new LinkedHashSet<String>(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<String> actualDependentBeans = new LinkedHashSet<String>(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); |