【死磕 Spring】—– IOC 之循環依賴處理

原文出自:http://cmsblogs.com


這篇分析 doCreateBean() 第三個過程:循環依賴處理。其實循環依賴並不僅僅只是在 doCreateBean() 中處理,其實在整個加載 bean 的過程中都有涉及,所以下篇內容並不僅僅只侷限於 doCreateBean(),而是從整個 Bean 的加載過程進行分析。

什麼是循環依賴

循環依賴其實就是循環引用,就是兩個或者兩個以上的 bean 互相引用對方,最終形成一個閉環,如 A 依賴 B,B 依賴 C,C 依賴 A,如下:

循環依賴 其實就是一個死循環的過程,在初始化 A 的時候發現引用了 B,這時就會去初始化 B,然後又發現 B 引用 C,跑去初始化 C,初始化 C 的時候發現引用了 A,則又會去初始化 A,依次循環永不退出,除非有終結條件。

Spring 循環依賴的場景有兩種:

  1. 構造器的循環依賴
  2. field 屬性的循環依賴

對於構造器的循環依賴,Spring 是無法解決的,只能拋出 BeanCurrentlyInCreationException 異常表示循環依賴,所以下面我們分析的都是基於 field 屬性的循環依賴。

在博客 【死磕 Spring】----- IOC 之開啓 bean 的加載 中提到,Spring 只解決 scope 爲 singleton 的循環依賴,對於scope 爲 prototype 的 bean Spring 無法解決,直接拋出 BeanCurrentlyInCreationException 異常。爲什麼 Spring 不處理 prototype bean,其實如果理解 Spring 是如何解決 singleton bean 的循環依賴就明白了。這裏先賣一個關子,我們先來關注 Spring 是如何解決 singleton bean 的循環依賴的。

解決循環依賴

我們先從加載 bean 最初始的方法 doGetBean() 開始。

doGetBean() 中,首先會根據 beanName 從單例 bean 緩存中獲取,如果不爲空則直接返回。

Object sharedInstance = getSingleton(beanName);

調用 getSingleton() 方法從單例緩存中獲取,如下:

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

這個方法主要是從三個緩存中獲取,分別是:singletonObjects、earlySingletonObjects、singletonFactories,三者定義如下:

    /** Cache of singleton objects: bean name --> bean instance */    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);    /** Cache of singleton factories: bean name --> ObjectFactory */    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);    /** Cache of early singleton objects: bean name --> bean instance */    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

意義如下:

  • singletonObjects:單例對象的cache
  • singletonFactories : 單例對象工廠的cache
  • earlySingletonObjects :提前暴光的單例對象的Cache

他們就是 Spring 解決 singleton bean 的關鍵因素所在,我稱他們爲三級緩存,第一級爲 singletonObjects,第二級爲 earlySingletonObjects,第三級爲 singletonFactories。這裏我們可以通過 getSingleton() 看到他們是如何配合的,這分析該方法之前,提下其中的 isSingletonCurrentlyInCreation()allowEarlyReference

  • isSingletonCurrentlyInCreation():判斷當前 singleton bean 是否處於創建中。bean 處於創建中也就是說 bean 在初始化但是沒有完成初始化,有一個這樣的過程其實和 Spring 解決 bean 循環依賴的理念相輔相成,因爲 Spring 解決 singleton bean 的核心就在於提前曝光 bean。
  • allowEarlyReference:從字面意思上面理解就是允許提前拿到引用。其實真正的意思是是否允許從 singletonFactories 緩存中通過 getObject() 拿到對象,爲什麼會有這樣一個字段呢?原因就在於 singletonFactories 纔是 Spring 解決 singleton bean 的訣竅所在,這個我們後續分析。

getSingleton() 整個過程如下:首先從一級緩存 singletonObjects 獲取,如果沒有且當前指定的 beanName 正在創建,就再從二級緩存中 earlySingletonObjects 獲取,如果還是沒有獲取到且運行 singletonFactories 通過 getObject() 獲取,則從三級緩存 singletonFactories 獲取,如果獲取到則,通過其 getObject() 獲取對象,並將其加入到二級緩存 earlySingletonObjects 中 從三級緩存 singletonFactories 刪除,如下:

singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);

這樣就從三級緩存升級到二級緩存了。

上面是從緩存中獲取,但是緩存中的數據從哪裏添加進來的呢?一直往下跟會發現在 doCreateBean() ( AbstractAutowireCapableBeanFactory ) 中,有這麼一段代碼:

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {    if (logger.isDebugEnabled()) {        logger.debug("Eagerly caching bean '" + beanName +                        "' to allow for resolving potential circular references");    }    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}

如果 earlySingletonExposure==true 的話,則調用 addSingletonFactory() 將他們添加到緩存中,但是一個 bean 要具備如下條件纔會添加至緩存中:

  • 單例
  • 運行提前暴露 bean
  • 當前 bean 正在創建中

addSingletonFactory() 代碼如下:

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

從這段代碼我們可以看出 singletonFactories 這個三級緩存纔是解決 Spring Bean 循環依賴的訣竅所在。同時這段代碼發生在 createBeanInstance() 方法之後,也就是說這個 bean 其實已經被創建出來了,但是它還不是很完美(沒有進行屬性填充和初始化),但是對於其他依賴它的對象而言已經足夠了(可以根據對象引用定位到堆中對象),能夠被認出來了,所以 Spring 在這個時候選擇將該對象提前曝光出來讓大家認識認識。

介紹到這裏我們發現三級緩存 singletonFactories 和 二級緩存 earlySingletonObjects 中的值都有出處了,那一級緩存在哪裏設置的呢?在類 DefaultSingletonBeanRegistry 中可以發現這個 addSingleton() 方法,源碼如下:

    protected void addSingleton(String beanName, Object singletonObject) {        synchronized (this.singletonObjects) {            this.singletonObjects.put(beanName, singletonObject);            this.singletonFactories.remove(beanName);            this.earlySingletonObjects.remove(beanName);            this.registeredSingletons.add(beanName);        }    }

添加至一級緩存,同時從二級、三級緩存中刪除。這個方法在我們創建 bean 的鏈路中有哪個地方引用呢?其實在前面博客 LZ 已經提到過了,在 doGetBean() 處理不同 scope 時,如果是 singleton,則調用 getSingleton(),如下:

前面幾篇博客已經分析了 createBean(),這裏就不再闡述了,我們關注方法 getSingleton() 代碼如下:

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {        Assert.notNull(beanName, "Bean name must not be null");        synchronized (this.singletonObjects) {            Object singletonObject = this.singletonObjects.get(beanName);            if (singletonObject == null) {                //....                try {                    singletonObject = singletonFactory.getObject();                    newSingleton = true;                }                //.....                if (newSingleton) {                    addSingleton(beanName, singletonObject);                }            }            return singletonObject;        }    }

至此,Spring 關於 singleton bean 循環依賴已經分析完畢了。所以我們基本上可以確定 Spring 解決循環依賴的方案了:Spring 在創建 bean 的時候並不是等它完全完成,而是在創建過程中將創建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 緩存中),這樣一旦下一個 bean 創建的時候需要依賴 bean ,則直接使用 ObjectFactory 的 getObject() 獲取了,也就是 getSingleton() 中的代碼片段了。

到這裏,關於 Spring 解決 bean 循環依賴就已經分析完畢了。最後來描述下就上面那個循環依賴 Spring 解決的過程:首先 A 完成初始化第一步並將自己提前曝光出來(通過 ObjectFactory 將自己提前曝光),在初始化的時候,發現自己依賴對象 B,此時就會去嘗試 get(B),這個時候發現 B 還沒有被創建出來,然後 B 就走創建流程,在 B 初始化的時候,同樣發現自己依賴 C,C 也沒有被創建出來,這個時候 C 又開始初始化進程,但是在初始化的過程中發現自己依賴 A,於是嘗試 get(A),這個時候由於 A 已經添加至緩存中(一般都是添加至三級緩存 singletonFactories ),通過 ObjectFactory 提前曝光,所以可以通過 ObjectFactory.getObject() 拿到 A 對象,C 拿到 A 對象後順利完成初始化,然後將自己添加到一級緩存中,回到 B ,B 也可以拿到 C 對象,完成初始化,A 可以順利拿到 B 完成初始化。到這裏整個鏈路就已經完成了初始化過程了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章