天天看點

吊打面試官-spring如何解決循環依賴?

作者:如意愛程式設計
吊打面試官-spring如何解決循環依賴?

1、什麼是循環依賴

spring容器的bean互相依賴形成閉環,稱為spring的循環依賴。為什麼Spring解決循環依賴比較麻煩呢?因為Spring建立一個Bean是需要通過反射來建構的,建構過程中無法感覺這個類具體是什麼類型的,它隻能夠執行個體化一個填充一個實體。

@service
class AServiceImpl implments AService{
		@Autowried
		private BService bService;
}

@service
class BServiceImpl implments BService{
	@Autowried
	private AService aService;
}           

如上例所示,建立 AServiceImpl完成後發現依賴BServiceImpl,于是建立BServiceImpl ,但是建立完成後又發現依賴于AServiceImpl。于是又去建立AServiceImpl,又發現BServiceImpl ,然後再去建立BServiceImpl…

2、如何解決

首先,我們明确兩個概念:

執行個體化:調用構造函數将對象建立出來

初始化:調用構造函數将對象建立出來後,給對象的屬性也被指派

在Spring中,建立了兩個容器(Map),一個起名為singletonObjects,一個起名為earlySingletonObjects。

singletonObjects:單例池,我們去存放已經建立完成,并且屬性也注入完畢的對象。

earlySingletonObjects:提前暴露的對象,存放已經建立完成,但是沒有注入好的對象。

通過這兩個Map容器,再次試圖建立一個被循環依賴的bean。

1)建立 A完成後,把自己存到「earlySingletonObjects」裡面去,然後發現依賴B,于是試圖從「singletonObjects」尋找,沒有找到;然後到「earlySingletonObjects」裡面尋找發現也沒有;

2)開始建立B ,先把自己存放到「earlySingletonObjects」裡面去,然後發現依賴A,試圖從「singletonObjects」尋找,沒有的找到;然後到「earlySingletonObjects」裡面尋找,發現了A對象;将「earlySingletonObjects」傳回的對象A設定到B中去,建立完成;

3)把A放置到「singletonObjects」裡面,然後把自己從「earlySingletonObjects」删除掉,傳回。

3、為什麼使用三級緩存

通過上面的解釋我們大概明白了循環依賴的解決方案,明明采用二級緩存就能夠解決循環依賴,但是Spring為什麼使用了三級緩存呢?

上面的設計方案二級緩存是能夠很好的解決循環依賴所帶來的問題,但當我們建立的bean所依賴的對象是一個需要被Aop代理的對象,怎麼辦?遇到這種情況,我們肯定不能夠直接把建立完成的對象放到緩存中去的,為什麼,因為我們期望的注入的是一個被代理後的對象,而不是一個原始對象。是以這裡并不能夠直接将一個原始對象放置到緩存中,我們可以直接進行判斷,如果需要Aop的話進行代理之後放入緩存。

但是Aop的操作是在哪裡做的?是在Spring聲明周期的最後一步來做的。如果我們進行判斷建立的話,Aop的代理邏輯就會在建立執行個體的時候就進行Aop的代理了,這明顯是不符合Spring對于Bean生命周期的定義的。 是以,Spring重新定義了一個緩存【singletonFactories】用來存放一個Bean的工廠對象,建立的對象之後,填充屬性之前會把建立好的對象放置到【singletonFactories】緩存中去,并不進行執行個體化,隻有在發生了循環引用,或者有對象依賴他的時候,才會調用工廠方法傳回一個代理對象,進而保證了Spring對于Bean生命周期的定義。

4、三級緩存

Map<String, Object> singletonObjects: 一級緩存,也就是我們平常了解的單例池,存放已經完整經曆了完整生命周期的bean對象。

Map<String, Object> earlySingletonObjects: 二級緩存,存儲早期暴露出來的bean對象,bean的生命周期未結束。(屬性還未填充完)

Map<String,ObjectFactory<?> > singletonFactories: 三級緩存,存儲生成bean的工廠。提前暴露的對象,存放已經建立完成,但是還沒有注入好的對象的工廠對象,通過這個工廠可以傳回這個對象。

注意:隻有單例bean會通過三級緩存提前暴露出來解決循環依賴的問題,而非單例的bean,每次從容器中擷取都是一個新的對象,都會重新建立,是以非單例bean是沒有緩存的,不會将期放到三級緩存中。

1)A在建立過程中需要B,于是A先将自己放到三級緩存裡面,去執行個體化B

2)B執行個體化的時候發現需要A,于是B先查一級緩存,沒有再查二級緩存,還是沒有,再查三級緩存,找到了A;然後把三級緩存裡面的這個A放到二級緩存裡面,并删除三級緩存裡面的A;

3)B順利初始化完畢,将自己放到一級緩存裡面(此時B裡面的A依然是建立中狀态);然後回來接着建立A,此時B已經建立結束,直接從一級緩存裡面拿到B,然後完成建立,并将A放入到一級緩存中