Spring 的循環依賴已經被說爛了,可能不少人也看吐了。但不少博客上說的仍是不夠清楚,沒有完整的表達出 Spring 的設計目的。只介紹了 What ,對於 Why 的介紹卻不太夠。java
本文會從設計角度,一步一步詳細分析 Spring 這個「三級緩存」的設計原則,說說爲何要這麼設計。spring
Spring 中的每個 Bean 都由一個BeanDefinition 建立而來,在註冊完成 BeanDefinition 後。會遍歷BeanFactory中的 beanDefinitionMap 對全部的 Bean 調用 getBean 進行初始化。緩存
簡單來講,一個 Bean 的建立流程主要分爲如下幾個階段:app
在完成上面的建立流程後,將 Bean 添加到緩存中 - singletonObjects,之後在 getBean 時先從緩存中查找,不存在才建立。函數
Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
先拋開 Spring 的源碼不談,先看看按照這個建立流程執行會遇到什麼問題post
首先是第一階段 - 實例化,就是調用 Bean Class的構造函數,建立實例而已,沒啥可說的。至於一些獲取 BeanDefinition 構造方法的邏輯,不是循環依賴的重點。this
第二階段 - 填充Bean,其目的是查找當前 Bean 引用的全部 Bean,利用 BeanFactory 獲取這些 Bean,而後注入到當前 Bean 的屬性中。spa
正常狀況下,沒有循環引用關係時沒什麼問題。好比如今正在進行 ABean 的 populate 操做,發現了 BBean 的引用,經過 BeanFactory 去 getBean(BBean) 就能夠完成,哪怕如今 BBean 尚未建立,在getBean中完成初始化也能夠。完成後將 BBean 添加到已建立完成的 Bean 緩存中 - singletonObjects。設計
最後再將獲取的 BBean 實例注入到 ABean 中就完成了這個 populate 操做,看着還挺簡單。代理
此時引用關係發生了點變化,ABean 也依賴了 BBean,兩個 Bean 的引用關係變成了互相引用,以下圖所示:
再來看看如今 populate 該怎麼執行:
首先仍是先初始化 BBean ,而後發現了 Bbean 引用的 ABean,如今 getBean(ABean),發現 ABean 也沒有建立,開始執行對 ABean 的建立:先實例化,而後對 ABean 執行 populate,可 populate 時又發現了 ABean 引用了 BBean,可此時 BBean 尚未建立完成,Bean 緩存中也並不存在。這樣就出現死循環了,兩個 Bean 相互引用,populate 操做徹底無法執行。
其實解決這個問題也很簡單,出現死循環的關鍵是兩個 Bean 互相引用,populate 時另外一個 Bean 還在建立中,沒有建立完成。
只須要增長一個中間狀態的緩存容器,用來存儲只執行了 instantiate 還未 populate 的那些 Bean。到了populate 階段時,若是完整狀態緩存中不存在,就從中間狀態緩存容器查找一遍,這樣就避免了死循環的問題。
以下圖所示,增長了一箇中間狀態的緩存容器 - earlySingletonObjects,用來存儲剛執行 instantiate 的 Bean,在 Bean 完成建立後,從 earlySingletonObjects 刪除,添加到 singletonObjects 中。
回到上面的例子,若是在 ABean 的 populate 階段又發現了 BBean 的引用,那麼先從 singletonObjects 查找,若是不存在,繼續從 earlySingletonObjects 中查找,找到之後注入到 ABean 中,而後 ABean 建立完成(BeanPostProcessor待會再說)。如今將 ABean 添加到 singletonObjects 中,接着返回到建立 BBean 的過程。最後把返回的 ABean 注入到 BBean 中,就完成了 BBean 的 populate 操做,以下圖所示:
循環依賴的問題,就這麼輕易的解決了,看着很簡單,只是加了一箇中間狀態而已。但除了 instantiate 和 populate 階段,還有最後一個執行 BeanPostProcessor 階段,這個階段可能會加強/替換原始 Bean
這個階段分爲執行初始化方法 - initMethod,和執行 BeanFactory 中定義的 BeanPostProcessor(BPP)。執行初始化方法沒啥可說的,重點看看 執行BeanPostProcessor 部分。
BeanPostProcessor 算是 Spring 的靈魂接口了,不少擴展的操做和功能都是經過這個接口,好比 AOP。在populate 完成以後,Spring 會對 Bean 順序執行全部的 BeanPostProcessor,而後返回 BeanPostProcessor 返回的新 Bean 實例(可能有修改也可能沒修改)
//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; //對當前 Bean 順序的執行全部的 BeanPostProcessor,並返回 for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessBeforeInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; }
Spring 的 AOP 加強功能,也是利用 BeanPostProcessor 完成的,若是該 Bean 有 AOP 加強的配置,那麼執行完 BeanPostProcessor 以後就會返回一個新的 Bean,最後存儲到 singletonObjects 中的也是這個加強以後的 Bean
可咱們上面的「中間狀態緩存」解決方案中,存儲的卻只是剛執行完成 instantiate 的 Bean。若是在上面循環依賴的例子中,populate ABean 時因爲 BBean 只完成了實例化,因此會從 earlySingletonObjects 獲取只完成初始化的 BBean 並注入到 ABean中。
若是 BBean 有 AOP 的配置,那麼此時注入到 ABean 中的 只是一個只實例化未 AOP 加強的對象。當 BBean 執行 BeanPostProcessor 後,又會建立一個加強的 BBean 實例,最終添加到 singletonObjects 中的,是加強的 BBean 實例,而不是那個剛實例化的 BBean 實例
以下圖所示,BBean 中注入的是黃色的只完成了初始化的 ABbean,而最終添加到 singletonObjects 倒是執行完 AOP 的加強 ABean 實例:
因爲 populate 以後還有一步 BeanPostProcessor 的加強,致使咱們上面的解決方案無效了。但也不是徹底無解,若是可讓加強型的 BeanPostProcessor 提早執行,而後添加到「中間狀態的緩存容器」中,是否是也能夠解決問題?
不過並非全部的 Bean 都有 AOP(及其餘執行 BPP 後返回新對象) 的需求,若是讓全部 Bean 都提早執行 BeanPostProcessor 並不合適。
因此這裏能夠採用一種「延遲處理」的方式,在中間增長一層 Factory,在這個 Factory 中完成「提早執行」的操做。
若是沒有提早調用某個 Bean 的 「延遲處理」Factory,那麼就不會致使提早執行 BeanPostProcessor,只有循環依賴場景下,纔會出現這種只完成初始化卻未徹底建立的 Bean ,纔會調用這個 Factory。這個 Factory 的模式就叫延遲處理,若是不調用 Factory 就不會提早執行 BPP。
instantiate 完成後,把這個 Factory 添加到「中間狀態的緩存容器」中;這樣當發生循環依賴時,原先獲取的中間狀態 Bean 實例就會變成這個 Factory,此時執行這個Factory 就能夠完成「提早執行 BeanPostProcessor」的操做,而且獲取執行後的新 Bean 實例
如今增長一個 ObjectFactory,用來實現延遲處理:
public interface ObjectFactory<T> { T getObject() throws BeansException; }
而後再建立一個 singletonFactories,做爲咱們新的中間狀態緩存容器,不過這個容器存儲的並非 Bean 實例,而是建立 Bean 的實現代碼
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
如今再寫一個「提早執行」BeanPostProcessor 的 ObjectFactory,添加到 singletonFactories 中。
//完成bean 的 instantiate 後 //建立一個對該 Bean 提早執行 BeanPostProcessor 的 ObjectFactory //最後添加到 singletonFactories 中 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean) ); protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); ...... } } } protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { //提早執行 BeanPostProcessor for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }
再次回到上面循環依賴的例子,若是在對 ABean 執行 populate 時,又發現了 BBean 的引用,那麼此時先從咱們這個新的延遲處理+提早執行的緩存容器中查找,不過如今找到的已經再也不是一個 BBean 實例了,而是咱們上面定義的那個getEarlyBeanReference 的 ObjectFactory ,經過調用 ObjectFactory.getObject() 來獲取提早執行 BeanPostProcessor 的這個 ABean 實例。
以下圖所示,在對 ABean 執行 populate 時,發現了對 BBean 的引用,那麼直接從 singletonFactories 中查找 BBean 的 ObjectFactory 並執行,獲取 BeanPostProcessor 加強/替換後的新 Bean
如今因爲咱們的中間狀態數據從 Bean 實例變成了 ObjectFactory,因此還須要在初始化以後,再檢查一下 singletonFactories 是否有當前 Bean,若是有的化須要手動調用一下 getObject 來獲取最終的 Bean 實例。
經過「延遲執行」+「提早執行」兩個操做,終於解決了這個循環依賴的問題。不過提早執行 BeanPostProcessor 會致使最終執行兩遍 BeanPostProcessor ,這個執行兩遍的問題還須要處理。
這個問題解決倒還算簡單,在那些會更換原對象實例的 BeanPostProcessor 中增長一個緩存,用來存儲已經加強的 Bean ,每次調用該 BeanPostProcessor 的時候,若是緩存中已經存在那就說明建立過了,直接返回上次建立的便可。Spring 爲此還單獨設計了一個接口,命名也很形象 - SmartInstantiationAwareBeanPostProcessor
若是你定義的 BeanPostProcessor 會加強並替換原有的 Bean 實例,必定要實現這個接口,在實現內進行緩存,避免重複加強
貌似如今問題已經解決了,一開始設計的 earlySingletonObjects 也不須要了,直接使用咱們這個中間狀態緩存工廠 - singletonFactories 就搞定了問題。
不過……若是依賴關係再複雜一點,好比像下面這樣,ABean 中有兩個屬性都引用了 BBean
那麼在對 ABean 執行 populate 時,先處理 refB 這個屬性;此時從 singletonFactories 中查找到 BBean 的這個提早執行 BeanPostProcessor 的 ObjectFactory,調用 getObject 獲取到提早執行 BeanPostProcessor 的 BBean 實例,注入到 refB 屬性中。
那到了 refB1 這個屬性時,因爲 BBean 仍是一個沒有建立完成的狀態(singletonObjects 中不存在),因此仍然須要獲取 BBean 的 ObjectFactory,執行 getObject,致使又對 BBean 執行了一遍 BeanPostProcessor。
爲了處理這個屢次引用的問題,仍是須要有一箇中間狀態的緩存容器 - earlySingletonObjects。不過這個緩存容器和一開始提到的那個 earlySingletonObjects 有一點點不一樣;一開始提到的 earlySingletonObjects 是存儲只執行了 instantiate 狀態的 Bean 實例,而咱們如今存儲的是執行 instantiate 以後,又提早執行了 BeanPostProcessor 的那些 Bean。
在提早執行了 BeanPostProcessor 以後,將返回的新的 Bean 實例也添加到 earlySingletonObjects 這個緩存容器中。這樣就算處於中間狀態時有屢次引用(屢次 getBean),也能夠從 earlySingletonObjects 獲取已經執行完 BeanPostProcessor 的那個 Bean,不會形成重複執行的問題。
回顧一下上面一步步解決循環依賴的流程,最終咱們經過一個延遲處理的緩存容器,加一個提早執行完畢BeanPostProcessor 的中間狀態容器就完美解決了循環依賴的問題
至於 singletonObjects 這個緩存容器,它只用來存儲全部建立完成的 Bean,和處理循環依賴關係並不大。
至於這個處理機制,叫不叫「三級緩存」……見仁見智吧,Spring 在源碼/註釋中也沒有(3-level cache之類的字眼)。並且關鍵的循環依賴處理,只是「二級」(延遲處理的 Factory + 提早執行 BeanPostProcessor 的Bean),所謂的「第三級」是應該是指 singletonObjects。
下面用一張圖,簡單總結一下處理循環依賴的核心機制:
不過提早執行 BeanPostProcessor 這個操做,算不算打破了原有的設計呢?本來 BeanPostProcessor 但是在建立Bean 的最後階段執行的,可如今爲了處理循環依賴,給移動到 populate 以前了。雖然是一個不太優雅的設計,但用來解決循環依賴也不錯。
儘管 Spring 支持了循環依賴(僅支持屬性依賴方式,構造方法依賴不支持,由於實例化都完成不了),但實際項目中,這種循環依賴的關係每每是不合理的,應該從設計上就避免。
原創不易,禁止未受權的轉載。若是個人文章對您有幫助,就請點贊/收藏/關注鼓勵支持一下吧❤❤❤❤❤❤