作者:空無
來源:SegmentFault 思否社區(qū)
前言
Spring 的循環(huán)依賴已經被說爛了,可能很多人也看吐了。但很多博客上說的還是不夠清楚,沒有完整的表達出 Spring 的設計目的。只介紹了 What ,對于 Why 的介紹卻不太夠。
本文會從設計角度,一步一步詳細分析 Spring 這個“三級緩存”的設計原則,說說為什么要這么設計。
Bean 創(chuàng)建流程
Spring 中的每一個 Bean 都由一個BeanDefinition 創(chuàng)建而來,在注冊完成 BeanDefinition 后。會遍歷BeanFactory中的 beanDefinitionMap 對所有的 Bean 調用 getBean 進行初始化。
簡單來說,一個 Bean 的創(chuàng)建流程主要分為以下幾個階段:
- Instantiate Bean - 實例化 Bean,通過默認的構造函數(shù)或者構造函數(shù)注入的方式
- Populate Bean - 處理 Bean 的屬性依賴,可以是Autowired注入的,也可以是 XML 中配置的,或者是手動創(chuàng)建的 BeanDefinition 中的依賴(propertyValues)
- Initialize Bean - 初始化 Bean,執(zhí)行初始化方法,執(zhí)行 BeanPostProcessor 。這個階段是各種 Bean 的后置處理,比如 AOP 的代理對象替換就是在這個階段
在完成上面的創(chuàng)建流程后,將 Bean 添加到緩存中 - singletonObjects,以后在 getBean 時先從緩存中查找,不存在才創(chuàng)建。Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
先拋開 Spring 的源碼不談,先看看按照這個創(chuàng)建流程執(zhí)行會遇到什么問題
1. Instantiate Bean
首先是第一階段 - 實例化,就是調用 Bean Class的構造函數(shù),創(chuàng)建實例而已,沒啥可說的。至于一些獲取 BeanDefinition 構造方法的邏輯,不是循環(huán)依賴的重點。2. Populate Bean
第二階段 - 填充Bean,其目的是查找當前 Bean 引用的所有 Bean,利用 BeanFactory 獲取這些 Bean,然后注入到當前 Bean 的屬性中。正常情況下,沒有循環(huán)引用關系時沒什么問題。比如現(xiàn)在正在進行 ABean 的 populate 操作,發(fā)現(xiàn)了 BBean 的引用,通過 BeanFactory 去 getBean(BBean) 就可以完成,哪怕現(xiàn)在 BBean 還沒有創(chuàng)建,在getBean中完成初始化也可以。完成后將 BBean 添加到已創(chuàng)建完成的 Bean 緩存中 - singletonObjects。
最后再將獲取的 BBean 實例注入到 ABean 中就完成了這個 populate 操作,看著還挺簡單。此時引用關系發(fā)生了點變化,ABean 也依賴了 BBean,兩個 Bean 的引用關系變成了互相引用,如下圖所示:
再來看看現(xiàn)在 populate 該怎么執(zhí)行:
首先還是先初始化 BBean ,然后發(fā)現(xiàn)了 Bbean 引用的 ABean,現(xiàn)在 getBean(ABean),發(fā)現(xiàn) ABean 也沒有創(chuàng)建,開始執(zhí)行對 ABean 的創(chuàng)建:先實例化,然后對 ABean 執(zhí)行 populate,可 populate 時又發(fā)現(xiàn)了 ABean 引用了 BBean,可此時 BBean 還沒有創(chuàng)建完成,Bean 緩存中也并不存在。這樣就出現(xiàn)死循環(huán)了,兩個 Bean 相互引用,populate 操作完全沒法執(zhí)行。其實解決這個問題也很簡單,出現(xiàn)死循環(huán)的關鍵是兩個 Bean 互相引用,populate 時另一個 Bean 還在創(chuàng)建中,沒有創(chuàng)建完成。只需要增加一個中間狀態(tài)的緩存容器,用來存儲只執(zhí)行了 instantiate 還未 populate 的那些 Bean。到了populate 階段時,如果完整狀態(tài)緩存中不存在,就從中間狀態(tài)緩存容器查找一遍,這樣就避免了死循環(huán)的問題。如下圖所示,增加了一個中間狀態(tài)的緩存容器 - earlySingletonObjects,用來存儲剛執(zhí)行 instantiate 的 Bean,在 Bean 完成創(chuàng)建后,從 earlySingletonObjects 刪除,添加到 singletonObjects 中。
回到上面的例子,如果在 ABean 的 populate 階段又發(fā)現(xiàn)了 BBean 的引用,那么先從 singletonObjects 查找,如果不存在,繼續(xù)從 earlySingletonObjects 中查找,找到以后注入到 ABean 中,然后 ABean 創(chuàng)建完成(BeanPostProcessor待會再說)?,F(xiàn)在將 ABean 添加到 singletonObjects 中,接著返回到創(chuàng)建 BBean 的過程。最后把返回的 ABean 注入到 BBean 中,就完成了 BBean 的 populate 操作,如下圖所示:
循環(huán)依賴的問題,就這么輕易的解決了,看著很簡單,只是加了一個中間狀態(tài)而已。但除了 instantiate 和 populate 階段,還有最后一個執(zhí)行 BeanPostProcessor 階段,這個階段可能會增強/替換原始 Bean3. Initialize Bean
這個階段分為執(zhí)行初始化方法 - initMethod,和執(zhí)行 BeanFactory 中定義的 BeanPostProcessor(BPP)。執(zhí)行初始化方法沒啥可說的,重點看看 執(zhí)行BeanPostProcessor 部分。BeanPostProcessor 算是 Spring 的靈魂接口了,很多擴展的操作和功能都是通過這個接口,比如 AOP。在populate 完成之后,Spring 會對 Bean 順序執(zhí)行所有的 BeanPostProcessor,然后返回 BeanPostProcessor 返回的新 Bean 實例(可能有修改也可能沒修改)//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
//對當前 Bean 順序的執(zhí)行所有的 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 增強的配置,那么執(zhí)行完 BeanPostProcessor 之后就會返回一個新的 Bean,最后存儲到 singletonObjects 中的也是這個增強之后的 Bean
可我們上面的“中間狀態(tài)緩存”解決方案中,存儲的卻只是剛執(zhí)行完成 instantiate 的 Bean。如果在上面循環(huán)依賴的例子中,populate ABean 時由于 BBean 只完成了實例化,所以會從 earlySingletonObjects 獲取只完成初始化的 BBean 并注入到 ABean中。如果 BBean 有 AOP 的配置,那么此時注入到 ABean 中的 只是一個只實例化未 AOP 增強的對象。當 BBean 執(zhí)行 BeanPostProcessor 后,又會創(chuàng)建一個增強的 BBean 實例,最終添加到 singletonObjects 中的,是增強的 BBean 實例,而不是那個剛實例化的 BBean 實例如下圖所示,BBean 中注入的是黃色的只完成了初始化的 ABbean,而最終添加到 singletonObjects 卻是執(zhí)行完 AOP 的增強 ABean 實例:
由于 populate 之后還有一步 BeanPostProcessor 的增強,導致我們上面的解決方案無效了。但也不是完全無解,如果可以讓增強型的 BeanPostProcessor 提前執(zhí)行,然后添加到“中間狀態(tài)的緩存容器”中,是不是也可以解決問題?
不過并不是所有的 Bean 都有 AOP(及其他執(zhí)行 BPP 后返回新對象) 的需求,如果讓所有 Bean 都提前執(zhí)行 BeanPostProcessor 并不合適。所以這里可以采用一種“延遲處理”的方式,在中間增加一層 Factory,在這個 Factory 中完成“提前執(zhí)行”的操作。如果沒有提前調用某個 Bean 的 “延遲處理”Factory,那么就不會導致提前執(zhí)行 BeanPostProcessor,只有循環(huán)依賴場景下,才會出現(xiàn)這種只完成初始化卻未完全創(chuàng)建的 Bean ,才會調用這個 Factory。這個 Factory 的模式就叫延遲處理,如果不調用 Factory 就不會提前執(zhí)行 BPP。instantiate 完成后,把這個 Factory 添加到“中間狀態(tài)的緩存容器”中;這樣當發(fā)生循環(huán)依賴時,原先獲取的中間狀態(tài) Bean 實例就會變成這個 Factory,此時執(zhí)行這個Factory 就可以完成“提前執(zhí)行 BeanPostProcessor”的操作,并且獲取執(zhí)行后的新 Bean 實例現(xiàn)在增加一個 ObjectFactory,用來實現(xiàn)延遲處理:public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
然后再創(chuàng)建一個 singletonFactories,作為我們新的中間狀態(tài)緩存容器,不過這個容器存儲的并不是 Bean 實例,而是創(chuàng)建 Bean 的實現(xiàn)代碼private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
現(xiàn)在再寫一個“提前執(zhí)行”BeanPostProcessor 的 ObjectFactory,添加到 singletonFactories 中。//完成bean 的 instantiate 后
//創(chuàng)建一個對該 Bean 提前執(zhí)行 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()) {
//提前執(zhí)行 BeanPostProcessor
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
再次回到上面循環(huán)依賴的例子,如果在對 ABean 執(zhí)行 populate 時,又發(fā)現(xiàn)了 BBean 的引用,那么此時先從我們這個新的延遲處理+提前執(zhí)行的緩存容器中查找,不過現(xiàn)在找到的已經不再是一個 BBean 實例了,而是我們上面定義的那個getEarlyBeanReference 的 ObjectFactory ,通過調用 ObjectFactory.getObject() 來獲取提前執(zhí)行 BeanPostProcessor 的這個 ABean 實例。
如下圖所示,在對 ABean 執(zhí)行 populate 時,發(fā)現(xiàn)了對 BBean 的引用,那么直接從 singletonFactories 中查找 BBean 的 ObjectFactory 并執(zhí)行,獲取 BeanPostProcessor 增強/替換后的新 Bean現(xiàn)在由于我們的中間狀態(tài)數(shù)據從 Bean 實例變成了 ObjectFactory,所以還需要在初始化之后,再檢查一下 singletonFactories 是否有當前 Bean,如果有的化需要手動調用一下 getObject 來獲取最終的 Bean 實例。通過“延遲執(zhí)行”+“提前執(zhí)行”兩個操作,終于解決了這個循環(huán)依賴的問題。不過提前執(zhí)行 BeanPostProcessor 會導致最終執(zhí)行兩遍 BeanPostProcessor ,這個執(zhí)行兩遍的問題還需要處理。這個問題解決倒還算簡單,在那些會更換原對象實例的 BeanPostProcessor 中增加一個緩存,用來存儲已經增強的 Bean ,每次調用該 BeanPostProcessor 的時候,如果緩存中已經存在那就說明創(chuàng)建過了,直接返回上次創(chuàng)建的即可。Spring 為此還單獨設計了一個接口,命名也很形象 - SmartInstantiationAwareBeanPostProcessor如果你定義的 BeanPostProcessor 會增強并替換原有的 Bean 實例,一定要實現(xiàn)這個接口,在實現(xiàn)內進行緩存,避免重復增強貌似現(xiàn)在問題已經解決了,一開始設計的 earlySingletonObjects 也不需要了,直接使用我們這個中間狀態(tài)緩存工廠 - singletonFactories 就搞定了問題。不過……如果依賴關系再復雜一點,比如像下面這樣,ABean 中有兩個屬性都引用了 BBean
那么在對 ABean 執(zhí)行 populate 時,先處理 refB 這個屬性;此時從 singletonFactories 中查找到 BBean 的這個提前執(zhí)行 BeanPostProcessor 的 ObjectFactory,調用 getObject 獲取到提前執(zhí)行 BeanPostProcessor 的 BBean 實例,注入到 refB 屬性中。
那到了 refB1 這個屬性時,由于 BBean 還是一個沒有創(chuàng)建完成的狀態(tài)(singletonObjects 中不存在),所以仍然需要獲取 BBean 的 ObjectFactory,執(zhí)行 getObject,導致又對 BBean 執(zhí)行了一遍 BeanPostProcessor。為了處理這個多次引用的問題,還是需要有一個中間狀態(tài)的緩存容器 - earlySingletonObjects。不過這個緩存容器和一開始提到的那個 earlySingletonObjects 有一點點不同;一開始提到的 earlySingletonObjects 是存儲只執(zhí)行了 instantiate 狀態(tài)的 Bean 實例,而我們現(xiàn)在存儲的是執(zhí)行 instantiate 之后,又提前執(zhí)行了 BeanPostProcessor 的那些 Bean。在提前執(zhí)行了 BeanPostProcessor 之后,將返回的新的 Bean 實例也添加到 earlySingletonObjects 這個緩存容器中。這樣就算處于中間狀態(tài)時有多次引用(多次 getBean),也可以從 earlySingletonObjects 獲取已經執(zhí)行完 BeanPostProcessor 的那個 Bean,不會造成重復執(zhí)行的問題。總結
回顧一下上面一步步解決循環(huán)依賴的流程,最終我們通過一個延遲處理的緩存容器,加一個提前執(zhí)行完畢BeanPostProcessor 的中間狀態(tài)容器就完美解決了循環(huán)依賴的問題至于 singletonObjects 這個緩存容器,它只用來存儲所有創(chuàng)建完成的 Bean,和處理循環(huán)依賴關系并不大。至于這個處理機制,叫不叫“三級緩存”……見仁見智吧,Spring 在源碼/注釋中也沒有(3-level cache之類的字眼)。而且關鍵的循環(huán)依賴處理,只是“二級”(延遲處理的 Factory + 提前執(zhí)行 BeanPostProcessor 的Bean),所謂的“第三級”是應該是指 singletonObjects。下面用一張圖,簡單總結一下處理循環(huán)依賴的核心機制:
不過提前執(zhí)行 BeanPostProcessor 這個操作,算不算打破了原有的設計呢?原本 BeanPostProcessor 可是在創(chuàng)建Bean 的最后階段執(zhí)行的,可現(xiàn)在為了處理循環(huán)依賴,給移動到 populate 之前了。雖然是一個不太優(yōu)雅的設計,但用來解決循環(huán)依賴也不錯。
盡管 Spring 支持了循環(huán)依賴(僅支持屬性依賴方式,構造方法依賴不支持,因為實例化都完成不了),但實際項目中,這種循環(huán)依賴的關系往往是不合理的,應該從設計上就避免。
點擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動和交流,掃描下方”二維碼“或在“公眾號后臺“回復“ 入群 ”即可加入我們的技術交流群,收獲更多的技術文章~