Spring IOC容器是如何解決循環依賴的問題?

  • 什么是循環依賴?
    先看代碼:
public class A {
    private B b;
    // 省略set/get方法
}

public class B {
    private A a;
    // 省略set/get方法
}

可以看到A類里有一個屬性是B類對象,而B類里也有一個屬性是A類對象,則我們可以稱A類對象與B類對象之間互相循環依賴。然后我們對把這倆個類納入到IOC容器中進行管理,現在進行xml配置:

<bean id="a" class="com.A">
    <property name="b" ref="b"/>
</bean>

<bean id="b" class="com.B">
    <property name="a" ref="a"/>
</bean>

當配置好xml以后,我們創建容器,并且調用getBean方法來獲取某個對象,那么會發生什么事情呢?正常邏輯應該是發生了死循環,a對象的創建需要依賴b對象,而b對象的創建同時也需要a對象。這簡直就是沒辦法解決嘛!但是SpringIOC卻解決了這個問題,并且你可以正常的獲取到相應的對象而不會發生錯誤。
那么SpringIOC是如何解決循環依賴的問題呢?

原理
SpringIOC解決循環依賴的思路就是依靠緩存,同時還得引出個概念即早期暴露引用。我們知道在IOC容器里bean的初始化的過程分為三個步驟:創建實例、屬性注入實例、回調實例實現的接口方法。解決思路就在這:當我們創建實例與屬性注入實例這倆個步驟之間的時候,我們引入緩存,將這些已經創建好但是并沒有注入屬性的實例放到緩存里,而這些放在緩存里但是沒有被注入屬性的實例對象,就是解決循環依賴的方法,打個比方:A對象的創建需要引用到B對象,而B對象的創建也需要A對象,而此時當B對象創建的時候直接從緩存里引用A對象(雖然不是完全體A對象,畢竟沒有賦值處理),當B對象完成創建以后再被A對象引用進去,則A對象也完成了創建。
這就是SpringIOC解決bean直接循環依賴的思路當然有一個小問題,IOC能夠解決的只能是屬性之間的循環依賴,如果有bean之間的構造器相互依賴則就解決不了只能報錯了。

  • 我們現在來看看Spring IOC的源碼

先看一下下面介紹源碼里的緩存的表:

源碼 級別 描述
singletonObjects 一級緩存 用于存放完全初始化好的 bean,從該緩存中取出的 bean 可以直接使用
earlySingletonObjects 二級緩存 存放原始的 bean 對象(尚未填充屬性),用于解決循環依賴
singletonFactories 三級緩存 存放 bean 工廠對象,用于解決循環依賴

省略不必要的代碼

protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
            throws BeansException {

    
    Object bean;

    // 從緩存中取得bean的實例
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        //進行后續處理,如果是正常的普通bean則返回普通的bean,如果是實現了FactoryBean接口的bean則返回的是getObject里的內容
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    else {
        if (!typeCheckOnly) {
            markBeanAsCreated(beanName);
        }

        try {
            final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
            checkMergedBeanDefinition(mbd, beanName, args);

            // 解決依賴的問題,這個跟我們說的依賴是不一樣的.可以忽略
            // ......
            

            // 創建單例 bean
            if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                    @Override
                    public Object getObject() throws BeansException {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            // 發生異常,銷毀bean
                            destroySingleton(beanName);
                            throw ex;
                        }
                    }
                });
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }
        }
        catch (BeansException ex) {
            cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        }
    }

    // Check if required type matches the type of the actual bean instance.
    // ......
    return (T) bean;
}

以上是doGetBean方法里的代碼,當然我省略了跟本章無關的代碼。
一步步來吧,先進行初始化a對象的操作,然后發現調用的是createBean(String beanName, RootBeanDefinition mbd, Object[] args)方法,而真正起作用的是doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)方法。而在這個方法里面包含了三個重要的方法createBeanInstance、populateBean、initializeBean,看過之前系列文章的人都知道這三個方法分別代表:創建實例、屬性注入、方法回調,這是bean初始化的核心方法。當然下面這段代碼是在createBeanInstance和populateBean中間的一段doCreateBean的代碼。


boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    // ......
    addSingletonFactory(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
            return getEarlyBeanReference(beanName, mbd, bean);
        }
    });
}

這段代碼在spring源碼注釋里描述是用來解決循環依賴的問題的。包含了一個匿名內部類ObjectFactory<T>(普通的工廠類返回的是getObject方法返回的對象),用getEarlyBeanReference實現了getObject方法。同時還調用了addSingletonFactory方法。分別來看一下各自方法的實現:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                if (exposedObject == null) {
                    return null;
                }
            }
        }
    }
    return exposedObject;
}
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);
        }
    }
}

可以看到在addSingletonFactory方法中,會將beanName與singletonFactory形成kv關系put進singletonFactories里面。并且將earlySingletonObjects里面的key值為beanName的kv進行移除。
此時a對象的早期暴露引用已經存在了singletonFactories三級緩存里面。此時a對象進行populateBean方法進行屬性注入,發現需要依賴b對象,緊接著就是去初始化b對象。繼續重復上面的步驟到b對象進行屬性注入這一步的時候(此時singletonFactories三級緩存里已經有了a對象的提前暴露引用和b對象的提前暴露引用的工廠對象),發現需要依賴a對象,此時去獲取a對象,看代碼:

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);

//繼續看這個方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        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);
}

先從singletonObjects一級緩存里取,如果沒有取到,則從earlySingletonObjects二級緩存里取,如果還是沒取到,則從singletonFactories三級緩存里取,取到以后進行getObject方法返回早期暴露對象引用,同時放進earlySingletonObjects二級緩存里,并且三級緩存里進行刪除該kv。
那么到此,a對象的早期暴露引用已經被b對象獲取到了,并且在singletonFactories三級緩存里已經沒有a對象的早期暴露引用的工廠對象了,a對象的早期暴露引用存在了二級緩存earlySingletonObjects里面,當然singletonFactories三級緩存依然有b對象的早期暴露引用的工廠對象。

繼續:b對象拿到了a對象的早期暴露引用,進行完屬性注入以后,則返回一個b對象了同時調用方法getSingleton(String beanName, ObjectFactory<?> singletonFactory),看源碼:

//我已經刪除了很多無關的代碼
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    // ......
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                // ......              
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
               // ... ...
               //... ...
                try {
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                finally {
                    // ... ...
                }
                if (newSingleton) {
                    addSingleton(beanName, singletonObject);
                }
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

其實就是倆個方法:singletonObject = singletonFactory.getObject();和addSingleton(beanName, singletonObject);至此我們不需要說明第一個了,著重來看一下addSingleton方法。

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

ok,上面源碼已經說明了此時singletonObjects一級緩存將要存入b對象,而二級緩存earlySingletonObjects和三級緩存singletonFactories則把相關緩存的對象移除。至此b對象則只存在一級緩存singletonObjects里面了。
當b對象完成了初始化以后,a對象則進行相關屬性的注入引入b的對象。完成實例化的同時a對象也會調用一次addSingleton方法,那么a對象完成以后,也就只有一級緩存singletonObjects里面才有a對象。

至此,屬性的循環依賴問題則完美的得到解決。

  • 文末
    感謝 【減肥是生命的主旋律】 的提問和回答
    有一個小問題,為什么在解決循環依賴問題的時候,我們會用到三級緩存singletonFactories呢?感覺二級緩存earlySingletonObjects就可以解決問題了呢?
    那么答案就在這里:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                if (exposedObject == null) {
                    return null;
                }
            }
        }
    }
    return exposedObject;
}

在將三級緩存放入二級緩存的時候,會判斷是否有SmartInstantiationAwareBeanPostProcessor這樣的后置處理器,換句話說這里是給用戶提供接口擴展的,所以采用了三級緩存。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容