Spring如何解決循環(huán)依賴

一、背景

我們都知道Spring可以通過xml迟螺,或者解析我們的注解,通過掃描所有資源文件,從而將所有匹配到的資源封裝成為一個BeanDefinition注冊到我們的BeanFactory中纵苛。
此時,Spring已經(jīng)知道了所有我們想要注冊到容器中的BeanDefinition,下一步就是將BeanDefinition實例化赶站,這樣才能提供出來給我們使用幔虏。

二纺念、Spring中Bean的實例化

我們發(fā)現(xiàn)Spring整個加載過程都在AbstractApplicationContext.refresh()中去完成贝椿。

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
    // Prepare this context for refreshing. 準備刷新
    prepareRefresh();
    
    // Tell the subclass to refresh the internal bean factory.
    /*
     * 刷新內(nèi)部BeanFactory
     * ClassPathXmlApplicationContext:1.新建BeanFactory,2.解析xml陷谱,3.封裝成BeanDefintion對象
     * AnnotationConfigApplicationContext: 獲取GenericApplicationContext中的beanFactory
     */
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    // Prepare the bean factory for use in this context.
    // 為BeanFactory進行必要的準備工作
    prepareBeanFactory(beanFactory);
    
    try {
        // Allows post-processing of the bean factory in context subclasses.
        // 進行額外的后置處理
        postProcessBeanFactory(beanFactory);
    
        // Invoke factory processors registered as beans in the context.
        // 執(zhí)行1.BeanDefinitionResgistryPostProcessor烙博、2.BeanFactoryPostProcessor的回調(diào)
        invokeBeanFactoryPostProcessors(beanFactory);
    
        // Register bean processors that intercept bean creation.
        // 實例化所有實現(xiàn)了BeanPostProcessor接口的類并注冊到容器中去
        registerBeanPostProcessors(beanFactory);
    
        // Initialize message source for this context. 國際化
        initMessageSource();
    
        // Initialize event multicaster for this context. 初始化事件類
        initApplicationEventMulticaster();
    
        // Initialize other special beans in specific context subclasses. 子容器自定義實現(xiàn)
        onRefresh();
    
        // Check for listener beans and register them. 注冊事件
        registerListeners();
    
        // Instantiate all remaining (non-lazy-init) singletons.
        //1.bean實例化,2.ioc 3.注解支持 4.BeanPostProssor執(zhí)行 5.AOP入口
        finishBeanFactoryInitialization(beanFactory);
    
        // Last step: publish corresponding event.
        finishRefresh();
      } catch (BeansException ex) {
        if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                    "cancelling refresh attempt: " + ex);
        }
    
        // Destroy already created singletons to avoid dangling resources.
                    destroyBeans();
        // Reset 'active' flag.
                    cancelRefresh(ex);
        // Propagate exception to caller.
        throw ex;
                } finally {
        // Reset common introspection caches in Spring's core, since we
        // might not ever need metadata for singleton beans anymore...
        resetCommonCaches();
    }
}

我們著重關(guān)注一下finishBeanFactoryInitialization方法烟逊,它是Spring實例化的入口方法渣窜。

  • 獲取BeanFactory中所有的beanDefinition名稱

  • 合并RootBeanDefinition

  • 非抽象的,單例的宪躯,非懶加載的就實例化

  • 是否實現(xiàn)了FactoryBean接口乔宿,如果是加一個&前綴調(diào)用內(nèi)部的getObject,否則直接獲取

  • 首先嘗試從緩存中獲取getSingleton(beanName),(首次獲取必然獲取不到)接著進入創(chuàng)建方法

  • 單例創(chuàng)建之前的操作:加入到正在創(chuàng)建的一個set集合中singletonsCurrentlyInCreation

  • 調(diào)到外部的匿名類中的實例化方法访雪,如果有值已經(jīng)創(chuàng)建成功singletonFactory.getObject();

  • 調(diào)到doCreateBean創(chuàng)建實例BeanWrapper

  • 允許早期引用加入單例工廠直接返回這個bean的引用详瑞。addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

  • 填充屬性的值populateBean

  • initializeBean

image.png

三、Spring容器如何解決循環(huán)依賴

什么是循環(huán)依賴

image.png

循環(huán)依賴就是循環(huán)引用臣缀,就是兩個或多個 bean 相互之間的持有對方坝橡,比如 CircleA 引用 C ircleB , CircleB 引用 CircleC, CircleC 引用 CircleA,則它們最終反映為一個環(huán)精置。

@Component
public class CircleClassA {
public CircleClassA() {
        System.out.println("====CircleClassA====");
    }
}

@Component
public class CircleClassB {
public CircleClassB() {
        System.out.println("====CircleClassB====");
    }
}

首先我們需要明確的一點是:Spring只會處理上述類型的循環(huán)依賴(單例计寇,非構(gòu)造函數(shù)注入)其它情況直接報錯。

Spring在處理Bean實例化的過程中是如何解決循環(huán)依賴的呢脂倦?我們需要著重關(guān)注如下3個Map番宁。

singletonObjects
earlySingletonObjects
singletonFactories

具體步驟如下:

  1. CircleClassA 在實例化的時候 首先從緩存中獲取不到,然后進入創(chuàng)建方法赖阻,接著將CircleClassA加入到singletonsCurrentlyInCreation中蝶押,并在singletonFactories加入一個getEarlyBeanReference,表示當前CircleClassA正在創(chuàng)建中政供。

  2. 當CircleClassA填充屬性的值populateBean時播聪,發(fā)現(xiàn)依賴了CircleClassB,觸發(fā)CircleClassB的實例化布隔。

  3. 實例化CircleClassB离陶,首先從緩存中獲取不到,然后進入創(chuàng)建方法衅檀,接著將CircleClassB加入到singletonsCurrentlyInCreation中招刨,并在singletonFactories加入一個getEarlyBeanReference,表示當前CircleClassB正在創(chuàng)建中哀军。

  4. 當CircleClassB填充屬性的值populateBean時沉眶,發(fā)現(xiàn)依賴了CircleClassA打却,觸發(fā)CircleClassA的實例化。

  5. 再次進入CircleClassA 的實例化方法谎倔,此時雖然singletonObjects中獲取不到CircleClassA柳击,但是檢測到CircleClassA存在早期暴露的實例因此嘗試從earlySingletonObjects中獲取,首次調(diào)用獲取不到從singletonFactories中獲取片习,取到之后將CircleClassA放入earlySingletonObjects捌肴,并提供給CircleClassB填充屬性的值populateBean時使用。(此時的CircleClassA只是個引用的地址藕咏,實際上并不是一個完整的CircleClassA)状知。

  6. 此時CircleClassB已經(jīng)完成了(內(nèi)部依賴的CircleClassA是個不完整的實例)并提供給CircleClassA填充屬性的值populateBean時使用。CircleClassA完成了CircleClassB的注入孽查,它變成了一個完整的實例饥悴。

  7. 又由于CircleClassB中引用了CircleClassA的一個地址。所以它也同時變成了一個完整的盲再。

  8. 實例化完成之后刪除早期引用map西设,并放入單例map中緩存singletonObjects。

image.png

程序員的核心競爭力其實還是技術(shù)洲胖,因此對技術(shù)還是要不斷的學習济榨,關(guān)注 “IT 巔峰技術(shù)” 公眾號 ,該公眾號內(nèi)容定位:中高級開發(fā)绿映、架構(gòu)師擒滑、中層管理人員等中高端崗位服務(wù)的,除了技術(shù)交流外還有很多架構(gòu)思想和實戰(zhàn)案例叉弦。

作者是 《 消息中間件 RocketMQ 技術(shù)內(nèi)幕》 一書作者丐一,同時也是 “RocketMQ 上海社區(qū)”聯(lián)合創(chuàng)始人,曾就職于拼多多淹冰、德邦等公司库车,現(xiàn)任上市快遞公司架構(gòu)負責人,主要負責開發(fā)框架的搭建樱拴、中間件相關(guān)技術(shù)的二次開發(fā)和運維管理柠衍、混合云及基礎(chǔ)服務(wù)平臺的建設(shè)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晶乔,一起剝皮案震驚了整個濱河市珍坊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌正罢,老刑警劉巖阵漏,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡履怯,警方通過查閱死者的電腦和手機回还,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叹洲,“玉大人柠硕,你說我怎么就攤上這事≌钗叮” “怎么了仅叫?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長糙捺。 經(jīng)常有香客問我,道長笙隙,這世上最難降的妖魔是什么洪灯? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮竟痰,結(jié)果婚禮上签钩,老公的妹妹穿的比我還像新娘。我一直安慰自己坏快,他們只是感情好铅檩,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著莽鸿,像睡著了一般昧旨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上祥得,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天兔沃,我揣著相機與錄音,去河邊找鬼级及。 笑死乒疏,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的饮焦。 我是一名探鬼主播怕吴,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼县踢!你這毒婦竟也來了转绷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤殿雪,失蹤者是張志新(化名)和其女友劉穎暇咆,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡爸业,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年其骄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扯旷。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡拯爽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钧忽,到底是詐尸還是另有隱情毯炮,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布耸黑,位于F島的核電站桃煎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏大刊。R本人自食惡果不足惜为迈,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缺菌。 院中可真熱鬧葫辐,春花似錦、人聲如沸伴郁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽焊傅。三九已至剂陡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間租冠,已是汗流浹背鹏倘。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留顽爹,地道東北人纤泵。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像镜粤,于是被迫代替她去往敵國和親捏题。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容