Spring DI 循環(huán)依賴

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

就是我們的A類依賴(比如@Autowired B b)B類恐疲,這個(gè)時(shí)候我們?nèi)?duì)A類的屬性注入時(shí),我們發(fā)現(xiàn)B可能沒有扯旷,怎么辦拯爽,去實(shí)例化,getBean()钧忽;
但是我在getBean()B的時(shí)候毯炮,我們又發(fā)現(xiàn)A也還沒實(shí)例化完!耸黑!
這個(gè)時(shí)候就A依賴B,B依賴A,就循環(huán)依賴了L壹濉!

2.三級(jí)緩存
//對(duì)于單例模式的Bean整個(gè)IOC容器中只創(chuàng)建一次大刊,不需要重復(fù)創(chuàng)建
Object sharedInstance = getSingleton(beanName);

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   Object singletonObject = this.singletonObjects.get(beanName);
        //判斷當(dāng)前單例Bean是否正在創(chuàng)建中为迈,也就是沒有初始化完成
        //比如A的構(gòu)造器依賴了B對(duì)象所以得先去創(chuàng)建B對(duì)象, 或則在A的populateBean過程中依賴了B對(duì)象缺菌,得先去創(chuàng)建B對(duì)象葫辐,這時(shí)的A就是處于創(chuàng)建中的狀態(tài)。
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
          //從二級(jí)緩存獲取
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
             //從三級(jí)緩存獲取
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

所以一級(jí)緩存為singletonObjects男翰,就是我們的單例對(duì)象工廠的cache

//單例對(duì)象工廠的cache
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);;

如果說另患,我們?cè)谝患?jí)緩存singletonFactories找不到的時(shí)候,并且對(duì)象正在創(chuàng)建中蛾绎,就再?gòu)亩?jí)緩存earlySingletonObjects中獲取
這個(gè)就是我們的二級(jí)緩存 earlySingletonObjects,提前暴光的單例對(duì)象的Cache,也是正在創(chuàng)建中的cache

private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

如果還是獲取不到且允許singletonFactories通過getObject()獲取,就從三級(jí)緩存singletonFactory.getObject()(三級(jí)緩存)獲取

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

如果獲取到了我們?cè)購(gòu)娜?jí)緩存移除鸦列,放到二級(jí)緩存租冠!也就是三級(jí)緩存移到二級(jí)緩存

3.三級(jí)緩存賦值
3.1 一級(jí)緩存

doGetBean方法中,Bean實(shí)例化完成后會(huì)調(diào)用getSingleton方法

sharedInstance = getSingleton(beanName, () -> {
   try {
      //創(chuàng)建一個(gè)指定Bean實(shí)例對(duì)象薯嗤,如果有父級(jí)繼承顽爹,則合并子類和父類的定義
      //----------------------------往下走創(chuàng)建Bean-----------------------------------
      return createBean(beanName, mbd, args);
   }

getSingleton方法中在DefaultSingletonBeanRegistry.java調(diào)用addSingleton()方法

//方法會(huì)加入一級(jí)緩存,同時(shí)清空2.3級(jí)緩存
protected void addSingleton(String beanName, Object singletonObject) {
   synchronized (this.singletonObjects) {
      this.singletonObjects.put(beanName, singletonObject);
      this.singletonFactories.remove(beanName);
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.add(beanName);
   }
}

所以骆姐,一級(jí)緩存是存的我們完整的Bean對(duì)象镜粤,就是除了銷毀整個(gè)生命流程結(jié)束了的Bean

3.2 三級(jí)緩存

那么我們3級(jí)緩存是個(gè)什么東西呢?我們可以看下玻褪,它是在哪里put值的肉渴,我們發(fā)現(xiàn),singletonFactories是在

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(singletonFactory, "Singleton factory must not be null");
   synchronized (this.singletonObjects) {
      if (!this.singletonObjects.containsKey(beanName)) {
         this.singletonFactories.put(beanName, singletonFactory);
         this.earlySingletonObjects.remove(beanName);
         this.registeredSingletons.add(beanName);
      }
   }
}

賦值带射,而該方法就是在doCreateBean()中調(diào)用同规,但是調(diào)用時(shí)間是在實(shí)例化之后,在populateBean之前!券勺!所以绪钥,三級(jí)緩存為我們實(shí)例化了,但是沒有進(jìn)行DI的一個(gè)半成品的Bean的集合!拟赊!

所以為什么Spring沒有解決構(gòu)造方法的循環(huán)依賴問題锰瘸? 因?yàn)闃?gòu)造方法在實(shí)例化的時(shí)候調(diào)用,所以不會(huì)存到三級(jí)緩存!

1.A實(shí)例化后放入三級(jí)緩存singletonFactories中寸潦,進(jìn)行populateBean,發(fā)現(xiàn)我A依賴B侣灶,并且B是沒有實(shí)例化完成的
2.去實(shí)例化B,發(fā)現(xiàn)B又依賴于A,這個(gè)時(shí)候甸祭,我去一級(jí)緩存獲取A,發(fā)現(xiàn)A也還沒有bean完成,那么我會(huì)繼續(xù)去三級(jí)緩存獲取褥影,發(fā)現(xiàn)了A
3.那么拿到A以后池户,B就能順利的實(shí)例化完成,就算A不是完全的凡怎,但是B里面有A的對(duì)象引用校焦。
4.B實(shí)例化完成后,繼續(xù)A完成實(shí)例化统倒。
5.整個(gè)A跟B全部實(shí)例化成功

4.為什么要用三級(jí)緩存

從剛才那個(gè)例子來看寨典,我們二級(jí)緩存完全沒有任何問題,那么spring為什么還要加一個(gè)三層7看摇耸成!
我們忽略了一個(gè)點(diǎn),就是我bean實(shí)例化后的對(duì)象后面可能會(huì)改變浴鸿,也就是我們的aop井氢,會(huì)變成我們的代理類!岳链!
大家看過源碼應(yīng)該都知道花竞,我們的aop是哪里實(shí)現(xiàn)的?
是在我們初始化的方法掸哑,不是實(shí)例化的方法约急,也不是DI的方法,而是在

initializeBean(beanName, exposedObject, mbd);  //里面的after方法苗分,既然是代理增強(qiáng)厌蔽,肯定得類初始化完成,不然沒有任何意義
....
Object current = beanProcessor.postProcessAfterInitialization(result, beanName);
...
AbstractAutoProxyCreator.java.java的wrapIfNecessary()

所以俭嘁,如果是AOP,如果只是用二級(jí)緩存的話躺枕,我們會(huì)得到一個(gè)什么問題?
singletonFactories 中是我們的原型對(duì)象,而singletonObjects中是我們的代理對(duì)象9赵啤罢猪!
因?yàn)閟ingletonFactories是在我們代理之前加入的,而singletonObjects是在我對(duì)象初始化完后才進(jìn)入的2娲瘛I排痢!
這樣我緩存依賴的時(shí)候薇缅,B我拿到的是原型對(duì)象危彩,但是其實(shí)我應(yīng)該依賴的是代理對(duì)象!泳桦!所以汤徽,如果只有二級(jí)緩存依賴的話,會(huì)導(dǎo)致2個(gè)緩存的對(duì)象不一致

5.三級(jí)緩存為什么能解決灸撰?
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

我們發(fā)現(xiàn)三級(jí)緩存谒府,跟1.2級(jí)的緩存不一樣,存的不是Object浮毯,而是一個(gè)匿名內(nèi)部類完疫,調(diào)用getEarlyBeanReference方法

最終調(diào)用到AbstractAutoProxyCreator.java的wrapIfNecessary,就是我們動(dòng)態(tài)代理的方法

循環(huán)依賴
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末债蓝,一起剝皮案震驚了整個(gè)濱河市壳鹤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饰迹,老刑警劉巖芳誓,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異啊鸭,居然都是意外死亡兆沙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門莉掂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人千扔,你說我怎么就攤上這事憎妙。” “怎么了曲楚?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵厘唾,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我龙誊,道長(zhǎng)抚垃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮鹤树,結(jié)果婚禮上铣焊,老公的妹妹穿的比我還像新娘。我一直安慰自己罕伯,他們只是感情好曲伊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著追他,像睡著了一般坟募。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上邑狸,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天懈糯,我揣著相機(jī)與錄音,去河邊找鬼单雾。 笑死赚哗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的铁坎。 我是一名探鬼主播蜂奸,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼硬萍!你這毒婦竟也來了扩所?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤朴乖,失蹤者是張志新(化名)和其女友劉穎祖屏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體买羞,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袁勺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了畜普。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片期丰。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吃挑,靈堂內(nèi)的尸體忽然破棺而出钝荡,到底是詐尸還是另有隱情,我是刑警寧澤舶衬,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布埠通,位于F島的核電站,受9級(jí)特大地震影響逛犹,放射性物質(zhì)發(fā)生泄漏端辱。R本人自食惡果不足惜梁剔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荣病。 院中可真熱鬧,春花似錦喷鸽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽混槐。三九已至编兄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間声登,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工件舵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脯厨。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓铅祸,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親合武。 傳聞我的和親對(duì)象是個(gè)殘疾皇子临梗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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