日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

spring 循环依赖_简单说说 Spring 的循环依赖

發布時間:2024/2/28 javascript 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 spring 循环依赖_简单说说 Spring 的循环依赖 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者 | 田偉然

回首向來蕭瑟處,歸去,也無風雨也無晴。?

杏仁工程師,關注編碼和詩詞。

前言

本文最耗時間的點就在于想一個好的標題, 既要燦爛奪目,又要光華內斂,事實證明這比砍需求還要難!

由于對象之間的依賴關系經常是錯綜復雜,使用不當會引發很多意想不到的問題, 一個很典型的問題就是循環依賴?(也可以稱之為循環引用)。

Spring 為我們提供了依賴注入,并且在某些情景(單例 Bean 的注入)下支持循環依賴的注入。

本文的主要目的是分析 Spring 在 Bean 的創建中是如何處理循環依賴的。

我會從循環依賴是什么,以及它的壞處,到最后通過Spring的源碼來看它是如何處理這個問題的。

循環依賴不僅僅是 Spring 的 Bean 之間會產生, 往大了看,系統模塊之間會產生循環依賴, 系統與系統之間也會產生循環依賴,這是一個典型的壞味道,我們應該盡量避免。

什么是循環依賴

循環依賴指的是多個對象之間的依賴關系形成一個閉環

下圖展示了兩個對象 A 和 B 形成的一個循環依賴

下圖展示了多個對象形成的一個循環依賴

現實中由于依賴層次深、關系復雜等因素, 導致循環依賴可能并不是那么一目了然。

為什么要避免循環依賴

循環依賴會為系統帶來很多意想不到的問題,下面我們來簡單討論一下一、循環依賴會產生多米諾骨牌效應換句話說就是牽一發而動全身,想象一下平靜的湖面落入一顆石子,漣漪會瞬間向周圍擴散。循環依賴形成了一個環狀依賴關系, 這個環中的某一點產生不穩定變化,都會導致整個環產生不穩定變化實際的體驗就是
  • 難以為代碼編寫測試,因為易變導致寫的測試也不穩定
  • 難以重構,因為互相依賴,你改動一個自然會影響其他依賴對象
  • 難以維護,你根本不敢想象你的改動會造成什么樣的后果
  • ......
二、循環依賴會導致內存溢出參考下面的代碼public class AService { private BService bService = new BService();}public class BService {private AService aService = new AService();}當你通過?new AService()?創建一個對象時你會獲得一個棧溢出的錯誤。如果你了解?Java?的初始化順序就應該知道為什么會出現這樣的問題。因為調用?new AService()?時會先去執行屬性 bService 的初始化, 而 bService 的初始化又會去執行 AService 的初始化, 這樣就形成了一個循環調用,最終導致調用棧內存溢出。

Spring的循環依賴示例

下面我們通過簡單的示例來展示 Spring 中的循環依賴注入, 我分別展示了一個構造器注入和 Field 注入的循環依賴示例
  • 構造器注入

@Service

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注入

@Servicepublic class AService {????@Autowired????private BService bService;}@Servicepublic class BService {????@Autowired????private AService aService;}

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 循環依賴注入為例,畫了一個完整的注入流程

注意上圖的黃色節點, 我們再來過一下這個流程
  • 在創建 A 的時候,會將?實例化的A?通過?addSingleFactory(黃色節點)方法緩存, 然后執行依賴注入B。
  • 注入會走創建流程, 最后B又會執行依賴注入A。
  • 由于第一步已經緩存了 A 的引用, 再次創建 A 時可以通過?getSingleton方法得到這個 A 的提前引用(拿到最開始緩存的 objectFactory, 通過它取得對象引用), 這樣 B 的依賴注入就完成了。
  • B 創建完成后, 代表 A 的依賴注入也完成了,那么 A 也創建成功了 (實際上 Spring 還有 initial 等步驟,不過與我們這次的討論主題相關性不大)
  • 這樣整個依賴注入的流程就完成了。

    總結

    又到了總結的時候了,雖然全文鋪的有點長,但是 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 的循环依赖的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。