Spring中循环依赖的处理 侵立删

转自: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的引用

 singletonFactoriesearlySingletonObjectssingletonObjects
getSingleton(beanName, true)
doCreateBean(beanName,mdb,args)
getSingleton(beanName, true);
addSingleton(beanName, singletonObject)

但是出现循环引用之后呢,就会出现这种情况:

 singletonFactoriesearlySingletonObjectssingletonObjects
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);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值