javascript
spring 循环依赖_简单说说 Spring 的循环依赖
作者 | 田偉然
回首向來蕭瑟處,歸去,也無風雨也無晴。?
杏仁工程師,關注編碼和詩詞。
前言
本文最耗時間的點就在于想一個好的標題, 既要燦爛奪目,又要光華內斂,事實證明這比砍需求還要難!
由于對象之間的依賴關系經常是錯綜復雜,使用不當會引發很多意想不到的問題, 一個很典型的問題就是循環依賴?(也可以稱之為循環引用)。
Spring 為我們提供了依賴注入,并且在某些情景(單例 Bean 的注入)下支持循環依賴的注入。
本文的主要目的是分析 Spring 在 Bean 的創建中是如何處理循環依賴的。
我會從循環依賴是什么,以及它的壞處,到最后通過Spring的源碼來看它是如何處理這個問題的。
循環依賴不僅僅是 Spring 的 Bean 之間會產生, 往大了看,系統模塊之間會產生循環依賴, 系統與系統之間也會產生循環依賴,這是一個典型的壞味道,我們應該盡量避免。
什么是循環依賴
循環依賴指的是多個對象之間的依賴關系形成一個閉環。
下圖展示了兩個對象 A 和 B 形成的一個循環依賴
下圖展示了多個對象形成的一個循環依賴現實中由于依賴層次深、關系復雜等因素, 導致循環依賴可能并不是那么一目了然。為什么要避免循環依賴
循環依賴會為系統帶來很多意想不到的問題,下面我們來簡單討論一下一、循環依賴會產生多米諾骨牌效應換句話說就是牽一發而動全身,想象一下平靜的湖面落入一顆石子,漣漪會瞬間向周圍擴散。循環依賴形成了一個環狀依賴關系, 這個環中的某一點產生不穩定變化,都會導致整個環產生不穩定變化實際的體驗就是- 難以為代碼編寫測試,因為易變導致寫的測試也不穩定
- 難以重構,因為互相依賴,你改動一個自然會影響其他依賴對象
- 難以維護,你根本不敢想象你的改動會造成什么樣的后果
- ......
Spring的循環依賴示例
下面我們通過簡單的示例來展示 Spring 中的循環依賴注入, 我分別展示了一個構造器注入和 Field 注入的循環依賴示例構造器注入
public class AService {
????private final BService bService;????@Autowired????public AService(BService bService) {this.BService = bService????}}@Servicepublic class BService {????private final AService aService;????@Autowired????public BService(AService aService) {this.aService = aService;????}}Field注入
Setter注入和 Feild 注入類似
如果你啟動 Spring 容器的話,?構造器注入的方式會拋出異常 BeanCreationException , 提示你出現了循環依賴。
但是 Field 注入的方式就會正常啟動,并注入成功。
這說明 Spring 雖然能夠處理循環依賴,但前提條件時你得按照它能夠處理的方式去做才行。
比如 prototype 的 Bean 也不能處理循環依賴的注入,這點我們需要注意。
一個檢測循環依賴的方法
在我們具體分析 Spring 的 Field 注入是如何解決循環依賴時, 我們來看看如何到檢測循環依賴。在一個循環依賴的場景中,我們可以確定以下約束1.? 依賴關系是一個圖的結構
2.? 依賴是有向的
3.? 循環依賴說明依賴關系產生了環
明確后,我們就能知道檢測循環依賴本質就是在檢測一個圖中是否出現了環, 這是一個很簡單的算法問題。
利用一個?HashSet?依次記錄這個依賴關系方向中出現的元素, 當出現重復元素時就說明產生了環, 而且這個重復元素就是環的起點。
參考下圖, 紅色的節點就代表是循環出現的點
以第一個圖為例,依賴方向為 A->B->C->A ,很容易檢測到 A 就是環狀點。
Spring是如何處理循環依賴的
Spring 能夠處理?單例Bean?的循環依賴(Field注入方式),本節我們就通過紙上談兵的方式來看看它是如何做到的
首先,我們將 Spring 創建 Bean 的生命周期簡化為兩個步驟:實例化 -> 依賴注入, 如下圖所示實例化就相當于通過?new?創建了一個具體的對象, 而依賴注入就相當于為對象的屬性進行賦值操作。我們再將這個過程擴展到兩個相互依賴 Bean 的創建過程上去, 如下圖所示:A 在執行依賴注入時需要實例化 B, 而 B 在執行依賴注入時又會實例化 A ,形成了一個很典型的依賴環。產生環的節點就是 B 在執行依賴注入的階段, 如果我們將其"砍”掉, 就沒有環了, 如下圖所示:這樣做確實沒有循環依賴了,但卻帶來了另一個問題,B 是沒有經過依賴注入的, 也就是說 B 是不完整的, 這怎么辦呢?此時 A 已經創建完成并維護在 Spring 容器內,A 持有 B 的引用, 并且 Spring 維護著未進行依賴注入的 B 的引用當 Spring?主動創建?B 時可以直接取得 B 的引用 (省去了實例化的過程), 當執行依賴注入時, 也可以直接從容器內取得 A 的引用, 這樣 B 就創建完成了A 持有的未進行依賴注入的 B,和后面單獨創建 B 流程里面是同一個引用對象, 當 B 執行完依賴注入后,A 持有的 B 也就是一個完整的 Bean了。Show me the code
沒有代碼的泛泛而談是沒有靈魂的
我畫了一個簡化的流程圖來展示一個 Bean 的創建(省略了 Spring 的 BeanPostProcessor,Aware 等事件)過程, 希望你過一遍,然后我們再去看源碼。
入口直接從?getBean(String)?方法開始, 以?populateBean?結束, 用于分析循環依賴的處理是足夠的了
getBean(String)?是?AbstractBeanFactory?的方法, 它內部調用了doGetBean?方法, 下面是源碼:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { @Overridepublic Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);}protected T doGetBean(final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly){...// #1Object sharedInstance = getSingleton(beanName);...final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);if (mbd.isSingleton()) {// #2sharedInstance = getSingleton(beanName, new ObjectFactory() {@Overridepublic Object getObject() throws BeansException {// #3return createBean(beanName, mbd, args);}});}...return (T)bean;}}我簡化了?doGetBean?的方法體,與流程圖對應起來,使得我們可以輕松找到下面的調用流程
doGetBean -> getSingleton(String) -> getSingleton(String, ObjectFactory)getSingleton?是?DefaultSingletonBeanRegistry?的重載方法
DefaultSingletonBeanRegistry?維護了三個?Map?用于緩存不同狀態的 Bean, 稍后我們分析?getSingleton?時會用到
/** 維護著所有創建完成的Bean */private final MapObject> singletonObjects = new ConcurrentHashMapObject>(256);/** 維護著創建中Bean的ObjectFactory */private final MapObjectFactory>> singletonFactories = new HashMapObjectFactory>>(16);/** 維護著所有半成品的Bean */private final MapObject> earlySingletonObjects = new HashMapObject>(16);getSingleton(String)調用了重載方法?getSingleton(String, boolean), 而該方法實際就是一個查詢 Bean 的實現, 先看圖再看代碼:從圖中我們可以看見如下查詢層次singletonObjects => earlySingletonObjects => singletonFactories再結合源碼public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { @Overridepublic Object getSingleton(String beanName) {return getSingleton(beanName, true);}protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 從singletonObjects獲取已創建的BeanObject singletonObject = this.singletonObjects.get(beanName);// 如果沒有已創建的Bean, 但是該Bean正在創建中if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 從earlySingletonObjects獲取已經實例化的BeansingletonObject = this.earlySingletonObjects.get(beanName);// 如果沒有實例化的Bean, 但是參數allowEarlyReference為trueif (singletonObject == null && allowEarlyReference) {// 從singletonFactories獲取ObjectFactoryObjectFactory> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// 使用ObjectFactory獲取Bean實例singletonObject = singletonFactory.getObject();// 保存實例, 并清理ObjectFactorythis.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}return (singletonObject != NULL_OBJECT ? singletonObject : null);}}通過?getSingleton(String)?沒有找到Bean的話就會繼續往下調用getSingleton(String, ObjectFactory)?, 這也是個重載方法, 源碼如下:public Object getSingleton(String beanName, ObjectFactory> singletonFactory) { ... // 獲取緩存的BeanObject singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {...// 標記Bean在創建中beforeSingletonCreation(beanName);boolean newSingleton = false;...// 創建新的Bean, 實際就是調用createBean方法singletonObject = singletonFactory.getObject();newSingleton = true;...if (newSingleton) {// 緩存beanaddSingleton(beanName, singletonObject);}}return (singletonObject != NULL_OBJECT ? singletonObject : null);}流程很清晰,就沒必要再畫圖了,簡單來說就是根據 beanName 找不到 Bean 的話就使用傳入的 ObjectFactory 創建一個 Bean。
從最開始的代碼片段我們可以知道這個 ObjectFactory 的 getObject 方法實際就是調用了?createBean?方法
sharedInstance = getSingleton(beanName, new ObjectFactory() {@Overridepublic Object getObject() throws BeansException {// #3return createBean(beanName, mbd, args);}});createBean?是?AbstractAutowireCapableBeanFactory?實現的,內部調用了doCreateBean?方法doCreateBean?承擔了 bean 的實例化,依賴注入等職責。
參考下圖
createBeanInstance?負責實例化一個 Bean 對象。addSingletonFactory?會將單例對象的引用通過 ObjectFactory 保存下來, 然后將該 ObjectFactory 緩存在?Map?中(該方法在依賴注入之前執行)。
populateBean?主要是執行依賴注入。
下面是源碼, 基本與上面的流程圖保持一致, 細節的地方我也標了注釋了
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {@Overrideprotected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {...return doCreateBean(beanName, mbdToUse, args);}protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {...BeanWrapper instanceWrapper = null;if (instanceWrapper == null) {// 實例化BeaninstanceWrapper = createBeanInstance(beanName, mbd, args);}final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);// 允許單例Bean的提前暴露boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {// 新建并緩存ObjectFactoryaddSingletonFactory(beanName, new ObjectFactory() {@Overridepublic Object getObject() throws BeansException {// 如果忽略BeanPostProcessor邏輯, 該方法實際就是直接返回bean對象// 而這里的bean對象就是前面實例化的對象return getEarlyBeanReference(beanName, mbd, bean);}});}...// 依賴注入populateBean(beanName, mbd, instanceWrapper);...}}如果你仔細看了上面的代碼片段,相信你已經找到 Spring 處理循環依賴的關鍵點了。我們以 A,B 循環依賴注入為例,畫了一個完整的注入流程注意上圖的黃色節點, 我們再來過一下這個流程總結
又到了總結的時候了,雖然全文鋪的有點長,但是 Spring 處理單例 Bean 的循環依賴卻并不復雜,而且稍微擴展一下,我們還可以將這樣的處理思路借鑒一下從而處理類似的問題。
不可避免的文章還是留下了不少坑,比如
我沒有詳細解釋構造器注入為什么不能處理循環依賴
我沒有詳細說明 Spring 如何檢測循環依賴的細節
我也沒有說明 prototype 的 Bean 為什么不能處理循環依賴
.....
當然這些都能在 Spring 創建 Bean 的流程里面找到(getBean(String) 方法),細節的東西就留給讀者自己去源碼里面發現了哦
參考
Circular_dependency
https://en.wikipedia.org/wiki/Circular_dependency
全文完
以下文章您可能也會感興趣:
文字描述符了解一下
簡單聊聊 TCP 的可靠性
一篇文章帶你搞懂 Swagger 與 SpringBoot 整合
延時隊列:基于 Redis 的實現
你真的懂 Builder 設計模式嗎?論如何實現真正安全的 Builder 模式
鎖優化的簡單思路
iOS開發:Archive、ipa 和 App 包瘦身
壓力測試必知必會
分布式 Session 之 Spring Session 架構與設計
MongoDB應用介紹
我們正在招聘 Java 工程師,歡迎有興趣的同學投遞簡歷到 rd-hr@xingren.com 。
總結
以上是生活随笔為你收集整理的spring 循环依赖_简单说说 Spring 的循环依赖的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: window10安装python2.7_
- 下一篇: python2.7与python3的区别