Spring循環(huán)依賴(lài)的解決

Spring循環(huán)依賴(lài)的解決

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

循環(huán)依賴(lài),是依賴(lài)關(guān)系形成了一個(gè)圓環(huán)。比如:A對(duì)象有一個(gè)屬性B埋心,那么這時(shí)候我們稱(chēng)之為A依賴(lài)B,如果這時(shí)候B對(duì)象里面有一個(gè)屬性A忙上。那么這時(shí)候A和B的依賴(lài)關(guān)系就形成了一個(gè)循環(huán)拷呆,這就是所謂的循環(huán)依賴(lài)。如果這時(shí)候IOC容器創(chuàng)建A對(duì)象的時(shí)候疫粥,發(fā)現(xiàn)B屬性茬斧,然后創(chuàng)建B對(duì)象,發(fā)現(xiàn)里面有A屬性梗逮,然后創(chuàng)建B.....這么無(wú)限循環(huán)下去铡原。我們先用代碼演示一下:

public class A {
    private B b=new B();
}

public class B {
    private A a=new A();
}

public class Test {
    public static void main(String[] args) {
        A a = new A();
    }
}

運(yùn)行一下結(jié)果

image-20200110220224411

那么我們可以看到循環(huán)依賴(lài)存在的問(wèn)題了

  1. 棧內(nèi)存溢出
  2. 程序的維護(hù)性和擴(kuò)展性太差

顯然這種思路是不正確的蛹屿。

產(chǎn)生循環(huán)依賴(lài)產(chǎn)生的條件:

  1. 在容器中創(chuàng)建的對(duì)象是單例的
  2. 對(duì)象是循環(huán)依賴(lài)

精簡(jiǎn)版解決方案

如果我們自己寫(xiě)的話(huà)微峰,該如何解決的呢剥纷?

public class A {
    private B b;
    public void setB(B b) {
        this.b = b;
    }
}
public class B {
    private A a;
    public void setA(A a) {
        this.a = a;
    }
}
public class Test {
    public static void main(String[] args) {
        A a = new A();//創(chuàng)建a對(duì)象
        B b = new B();//因?yàn)閍對(duì)象依賴(lài)B涮毫,那么創(chuàng)建B
        b.setA(a);//創(chuàng)建B對(duì)象的時(shí)候,發(fā)現(xiàn)依賴(lài)A贷屎,那么把通過(guò)構(gòu)造方法生成的對(duì)象a賦值給B
         a.setB(b);//然后把生成的b對(duì)象注入到a里面
    }
}

Spring解決方案

當(dāng)使用Spring的 @Autowired 注解的時(shí)候罢防,其實(shí)Spring的實(shí)現(xiàn)原理和上面很相似,先通過(guò)生成相關(guān)的對(duì)象唉侄,然后再把里面需要依賴(lài)的對(duì)象設(shè)置進(jìn)去咒吐。

我們現(xiàn)在從Spring源碼來(lái)走一遍。属划。

我們現(xiàn)貼出最基本的測(cè)試代碼

@Component
public class A {
   @Autowired
   B b;
}
@Component
public class B {
    @Autowired
    A a;
}
public class RecyclerTest {
    @Test
    public void test() {
        ApplicationContext context = new AnnotationConfigApplicationContext("com.kailaisi.demo.recycler");
        //getbean得時(shí)候才進(jìn)行IOC容器中的對(duì)象的實(shí)例化工作
        A a = (A) context.getBean("a");
    }
}

在我們之前發(fā)布的SpringBoot啟動(dòng)流程源碼分析里面恬叹,我們提到過(guò)bean單例的生成是在Spring容器創(chuàng)建過(guò)程中來(lái)完成的。經(jīng)過(guò)多層的調(diào)用同眯,最終會(huì)調(diào)用到 doGetBean 這個(gè)方法里面绽昼。

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
      @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
   ...
   Object bean;
   //先從緩存中獲取是否定義了對(duì)應(yīng)的類(lèi),這里的緩存包括了半成品類(lèi)緩存(只生成了類(lèi)须蜗,但是還沒(méi)有進(jìn)行屬性注入的類(lèi))和成品類(lèi)緩存(已經(jīng)完成了屬性注入的類(lèi))
   Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
      ......
      //如果符合條件硅确,直接從對(duì)飲給的bean單例中獲取到對(duì)象,然后返回
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   }
   else {
      ...
      try {
         .....
         //創(chuàng)建單例bean明肮,解決循環(huán)依賴(lài)的根本方案
         if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, () -> {
               try {
                  //調(diào)用創(chuàng)建單例的方法
                  return createBean(beanName, mbd, args);
               }
               ...
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
         }
   ...
   return (T) bean;
}

    @Override
    protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
            throws BeanCreationException {
            ...
            //進(jìn)行bean的創(chuàng)建
            Object beanInstance = doCreateBean(beanName, mbdToUse, args);
            ...
    }
    
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {
        //bean的包裝類(lèi)
        BeanWrapper instanceWrapper = null;
        ...
        if (instanceWrapper == null) {
            //創(chuàng)建beanDefinition所對(duì)應(yīng)的的參數(shù)的bean實(shí)例菱农,這里通過(guò)構(gòu)造方法或者工廠方法或者cglib創(chuàng)建了對(duì)象
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        if (earlySingletonExposure) {
            //將對(duì)象放到registeredSingletons隊(duì)列中,并從earlySingletonObjects中移除
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        ...
            //注入A的依賴(lài)柿估,這里面會(huì)發(fā)現(xiàn)屬性循未,然后從doGetBean()方法開(kāi)始,生成B對(duì)象秫舌,然后循環(huán)走到這里的時(shí)候的妖,在隊(duì)列里面會(huì)同時(shí)存在A對(duì)象和B對(duì)象。然后B對(duì)象注入A成功足陨,返回后將生成的B注入到A羔味,此時(shí)完成了A和B的對(duì)象生成,并解決了循環(huán)依賴(lài)問(wèn)題
            populateBean(beanName, mbd, instanceWrapper);
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        ...
}

加載過(guò)程比較長(zhǎng)钠右,其實(shí)主要是在加載的過(guò)程中將對(duì)象的創(chuàng)建過(guò)程進(jìn)行了分類(lèi)處理,在創(chuàng)建的不同時(shí)期忘蟹,放入到隊(duì)列來(lái)進(jìn)行區(qū)分飒房。

  1. singletonObjects:單例對(duì)象列表
  2. singletonFactories:單例工廠隊(duì)列,對(duì)象剛開(kāi)始創(chuàng)建的時(shí)候媚值,會(huì)放入到這個(gè)隊(duì)列狠毯。
  3. earlySingletonObjects:產(chǎn)生了循環(huán)依賴(lài)的對(duì)象隊(duì)列,對(duì)象在創(chuàng)建之后褥芒,進(jìn)行注入過(guò)程中嚼松,發(fā)現(xiàn)產(chǎn)生了循環(huán)依賴(lài)嫡良,那么會(huì)將對(duì)象放入到這個(gè)隊(duì)列,并且從singletonFactories中移除掉献酗。
  4. singletonsCurrentlyInCreation:正在創(chuàng)建的對(duì)象隊(duì)列寝受,整個(gè)創(chuàng)建過(guò)程都存放在這個(gè)隊(duì)列里面,當(dāng)完成了所有的依賴(lài)注入以后罕偎,從這個(gè)隊(duì)列里面移除
  5. registeredSingletons:已經(jīng)創(chuàng)建成功的單例列表很澄。

知道了這幾個(gè)隊(duì)列以后,我們可以來(lái)整理測(cè)試?yán)又醒占埃珹和B對(duì)象是如何一步步創(chuàng)建甩苛,并解決其循環(huán)依賴(lài)的問(wèn)題了。

  1. 首先俏站,依次從singletonObjects讯蒲,earlySingletonObjects,singletonFactories隊(duì)列中去尋找a對(duì)象肄扎,發(fā)現(xiàn)都沒(méi)有墨林,返回了null。那么這時(shí)候就需要?jiǎng)?chuàng)建B對(duì)象
  2. a的創(chuàng)建的準(zhǔn)備:在創(chuàng)建之前反浓,將a放入到singletonsCurrentlyInCreation隊(duì)列萌丈,表明a正在進(jìn)行創(chuàng)建。
  3. 開(kāi)始創(chuàng)建a:通過(guò)反射創(chuàng)建對(duì)象a雷则。
  4. 進(jìn)行創(chuàng)建后的處理:創(chuàng)建a對(duì)象以后辆雾,將a放入到singletonFactories和registeredSingletons隊(duì)列,并從earlySingletonObjects中移除。然后進(jìn)行依賴(lài)注入工作月劈,發(fā)現(xiàn)有依賴(lài)B對(duì)象度迂。
    1. 這時(shí)候進(jìn)入了B對(duì)象的注入過(guò)程
    2. 首先,依次從singletonObjects猜揪,earlySingletonObjects惭墓,singletonFactories隊(duì)列中去尋找b對(duì)象,發(fā)現(xiàn)都沒(méi)有而姐,返回了null腊凶。那么這時(shí)候就需要?jiǎng)?chuàng)建B對(duì)象
    3. b的創(chuàng)建的準(zhǔn)備工作:在創(chuàng)建之前,將b放入到singletonsCurrentlyInCreation隊(duì)列拴念,表明b正在進(jìn)行創(chuàng)建
    4. 開(kāi)始創(chuàng)建b:通過(guò)反射創(chuàng)建對(duì)象b钧萍。
    5. 進(jìn)行創(chuàng)建后的處理:將b放入到singletonFactories和registeredSingletons隊(duì)列,并從earlySingletonObjects中移除。然后進(jìn)行依賴(lài)注入工作政鼠,發(fā)現(xiàn)有依賴(lài) A對(duì)象风瘦。
      1. 這時(shí)候進(jìn)入A的注入過(guò)程。公般。万搔。
      2. 從singletonObjects中查找a胡桨,發(fā)現(xiàn)a不存在但是singletonsCurrentlyInCreation隊(duì)列中有a,那么這時(shí)候說(shuō)明a是在創(chuàng)建過(guò)程中的瞬雹,此處又需要?jiǎng)?chuàng)建昧谊,屬于循環(huán)依賴(lài)了。然后去earlySingletonObjects查找挖炬,也沒(méi)發(fā)現(xiàn)揽浙。那么這時(shí)候去singletonFactories隊(duì)列中去尋找a對(duì)象,找到了意敛。這時(shí)候?qū)對(duì)象放入到earlySingletonObjects隊(duì)列馅巷,并從singletonFactories中移除。因?yàn)榘l(fā)現(xiàn)了a對(duì)象草姻,這里直接返回a钓猬,此時(shí)完成了b對(duì)象對(duì)A的依賴(lài)注入了
    6. b實(shí)例化完成,而且依賴(lài)也注入完成了撩独,那么進(jìn)行最后的處理敞曹。將b實(shí)例從singletonsCurrentlyInCreation隊(duì)列移除,表明b對(duì)象實(shí)例化結(jié)束综膀。然后將b放入到singletonObjects和registeredSingletons隊(duì)列澳迫,并從singletonFactories和earlySingletonObjects隊(duì)列移除。最后將b對(duì)象注入到a對(duì)象中剧劝。然后a完成了創(chuàng)建過(guò)程橄登。
  5. a實(shí)例化完成,而且依賴(lài)也注入完成了讥此,那么進(jìn)行最后的處理拢锹。將a實(shí)例從singletonsCurrentlyInCreation隊(duì)列移除,表明a對(duì)象實(shí)例化結(jié)束萄喳。然后將a放入到singletonObjects和registeredSingletons隊(duì)列卒稳,并從singletonFactories和earlySingletonObjects隊(duì)列移除。此時(shí)完成了a對(duì)象的創(chuàng)建他巨。

總結(jié)

上述就是spring解決循環(huán)依賴(lài)的整體過(guò)程充坑,跟我們之前的那個(gè)方法很相似,只是對(duì)于各種情況的處理更仔細(xì)染突。而且從這個(gè)過(guò)程也能理解spring對(duì)于對(duì)象的創(chuàng)建過(guò)程匪傍。

本文由 開(kāi)了肯 發(fā)布!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末觉痛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子茵休,更是在濱河造成了極大的恐慌薪棒,老刑警劉巖手蝎,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異俐芯,居然都是意外死亡棵介,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)吧史,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)邮辽,“玉大人,你說(shuō)我怎么就攤上這事贸营《质觯” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵钞脂,是天一觀的道長(zhǎng)揣云。 經(jīng)常有香客問(wèn)我,道長(zhǎng)冰啃,這世上最難降的妖魔是什么邓夕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮阎毅,結(jié)果婚禮上焚刚,老公的妹妹穿的比我還像新娘。我一直安慰自己扇调,他們只是感情好矿咕,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著肃拜,像睡著了一般痴腌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上燃领,一...
    開(kāi)封第一講書(shū)人閱讀 52,475評(píng)論 1 312
  • 那天士聪,我揣著相機(jī)與錄音,去河邊找鬼猛蔽。 笑死剥悟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的曼库。 我是一名探鬼主播区岗,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼毁枯!你這毒婦竟也來(lái)了慈缔?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤种玛,失蹤者是張志新(化名)和其女友劉穎藐鹤,沒(méi)想到半個(gè)月后瓤檐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡娱节,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年挠蛉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肄满。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谴古,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出稠歉,到底是詐尸還是另有隱情掰担,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布轧抗,位于F島的核電站恩敌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏横媚。R本人自食惡果不足惜纠炮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灯蝴。 院中可真熱鬧恢口,春花似錦、人聲如沸穷躁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)问潭。三九已至猿诸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間狡忙,已是汗流浹背梳虽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留灾茁,地道東北人窜觉。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像北专,于是被迫代替她去往敵國(guó)和親禀挫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

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