天天看點

Spring中的循環依賴問題

最近在研究Spring IOC容器,遇到了對象的循環依賴問題,通過看源碼才明白Spring是如何優雅的解決單例循環依賴問題。Spring将對象依賴分成屬性依賴和構造依賴,構造依賴問題無法解決,隻能抛出BeanCurrentlyInCreationException異常,在解決屬性依賴問題,Spring采用的是提前暴露對象的方法。

構造依賴問題:

首先上代碼:

有一個類Office

public class Office {

    public String name;

    public Boss boss;

    public Office(String name,Boss boss){
        this.name=name;
        this.boss=boss;
    }
    public Boss getBoss() {
        return boss;
    }

    public void setBoss(Boss boss) {
        this.boss = boss;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}
           
public class Boss {
    private String name;
    private Car car;
    private Office office;

    /*public Boss(){

    }*/
    public Boss(String name,Office office){
        this.name=name;
        this.Office=office;

    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    public Office getOffice() {
        return office;
    }

    public void setOffice(Office office) {
        this.office = office;
    }
}
           

以上兩個類存在構造依賴問題,bean.xml配置如下:

<bean id="office" class="org.lww.springTest.Office">
        <constructor-arg index="0" value="400工作室"></constructor-arg>
        <constructor-arg index="1" ref="boss"></constructor-arg>
    </bean>

    <bean id="boss" class="org.lww.springTest.Boss">
        <constructor-arg index="0" value="Liweiwei"></constructor-arg>
        <constructor-arg index="1" ref="office"></constructor-arg>
    </bean>
           

當采用Spring初始時,會抛出異常。我們接下來看看Spring是怎麼發現這個異常的。

首先Spring需要預初始化單例對象office,在建立之前,會将office放入到singletonsCurrentlyInCreation集合中,在建立office對象時,發現它有個參數car是引用類型,是以Spring會通過getBean(“car”)去獲得car對象,由于car對象尚未建立,則建立car對象,在建立car對象之前,也會将car放入到singletonsCurrentlyInCreation中,執行個體化car對象時,car構造依賴office,同樣也會通過getBean(“office”),由于上一個office對象還未建立成功,這個時候Spring需再重新建立office對象,在建立之前,将office放入到singletonsCurrentlyInCreation時,發現office在singletonsCurrentlyInCreation集合中已經存在了,這個時候Spring判斷對象之間出現了循環依賴,那麼抛出BeanCurrentlyInCreationException異常。

屬性依賴問題:

先上代碼:

public class Office {

    public String name;

    public Boss boss;

    public Office(){

    }
    public Office(String name,Boss boss){
        this.name=name;
        this.boss=boss;
    }
    public Boss getBoss() {
        return boss;
    }

    public void setBoss(Boss boss) {
        this.boss = boss;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}
           
public class Boss {
    private String name;
    private Car car;
    private Office office;

    /*public Boss(){

    }*/
    public Boss(String name,Office office){
        this.name=name;
        this.office=office;

    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    public Office getOffice() {
        return office;
    }

    public void setOffice(Office office) {
        this.office = office;
    }
}
           

下面是bean.xml配置:

<bean id="office" class="org.lww.springTest.Office">
      <property name="name" value="400工作室"></property>
      <property name="boss" ref="boss"></property>
    </bean>

    <bean id="boss" class="org.lww.springTest.Boss">
        <constructor-arg index="0" value="Liweiwei"></constructor-arg>
        <constructor-arg index="1" ref="office"></constructor-arg>
    </bean>
           

大家可以看到,這個時候office和boss類也存在循環依賴關系,但這時候office采用預設構造函數,不依賴于boss對象,這種屬于屬性依賴,屬性依賴Spring可以很好的解決,采用我們上文提到的提前暴露對象。下面分析具體的處理過程。

首先Spring建立office對象,采用其預設構造函數,建立成功後,Spring會通過以下代碼将對象提前暴露出來,盡管此時的對象還未完成屬性注入,屬于早期對象。這個時候對象放在singletonFactories的Map表中,value是函數對象ObjectFactory.

addSingletonFactory(beanName, new ObjectFactory<Object>() {
                @Override
                public Object getObject() throws BeansException {
                    return getEarlyBeanReference(beanName, mbd, bean);
                }
            });
           

接下來,Spring會通過函數populateBean來完成office對象的屬性注入,再注入boss屬性時,發現是一個引用對象,這個時候同樣會通過getBean(“boss”)來獲得boss對象,boss對象由于從未建立,則建立boss對象。在建立boss對象時,發現它構造依賴于office對象,這個時候Spring也會通過getBean(“office”)擷取office對象。由于存在office提前暴露出來,這個時候直接從singletonFactories的Map表中得到office對象并傳回,并不需要重新再建立office對象,這樣就避免了循環依賴問題,接下來boss對象可以成功被建立,則傳回到到office的屬性注入中。office屬性注入完成後,得到的office對象是成型的,接下來Spring會進一步判斷office在後期的處理過程中是否發生引用更改,所謂引用更改就是成型的office對象與早期暴露的office對象是否還是同一個對象。下面是驗證代碼。

if (earlySingletonExposure) {
             //擷取指定名稱的已注冊的單态模式Bean對象
            // allowEarlyReference為false,則不會解析singletonFactories
            Object earlySingletonReference = getSingleton(beanName, false);
            if (earlySingletonReference != null) {//判斷點1:首先确定這個對象能從earlySingletonObjects中取出對象來
                //根據名稱擷取的以注冊的Bean和正在執行個體化的Bean是同一個  
                if (exposedObject == bean) {
                    //再判斷這個對象和目前通過beanPostProcessor處理過的對象是否相同,如果相同,表示對象沒有經過修改,即A=A-,那麼循環引用成立。無需處理
                    exposedObject = earlySingletonReference;
                }
                //目前Bean依賴其他Bean,并且當發生循環引用時不允許新建立執行個體對象  
                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()) {//出現循環引用,且被引用的bean被修改
                        throw new BeanCurrentlyInCreationException(beanName,
                                "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.");
                    }
                }
            }
        }
           

如果是同一個引用對象,則循環引用成立,否則會抛出BeanCurrentlyInCreationException異常,大家可看到異常消息:

Bean with name ’ beanName ’ has been injected into other beans 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.

大意是:目前對象的早期版本被注入到其他對象引用中,也就是最終版本和原始版本不一樣導緻的,這個時候Spring隻能抛出異常。

好了,Spring的循環依賴處理過程就這些了,如果有什麼錯誤,歡迎指正。

繼續閱讀