最通俗的方式理解Spring循環(huán)依賴三級緩存
今天,有位粉絲找我,說要耽誤我5分鐘時間,想讓我?guī)椭斫庖幌耂pring循環(huán)依賴的三級緩存,繞暈了一個星期,沒有想明白。我想今天,用最通俗易懂的方式給大家重新梳理一下,保證讓你聽懂了。
1、什么是循環(huán)依賴?
循環(huán)依賴就是指循環(huán)引用,是兩個或多個Bean相互之間的持有對方的引用。循環(huán)依賴有三種形態(tài):
(1)相互依賴,也就是A 依賴 B,B 又依賴 A,它們之間形成了循環(huán)依賴。
(2)三者間依賴,也就是A 依賴 B,B 依賴 C,C 又依賴 A,形成了循環(huán)依賴。
(3)自我依賴,也是A依賴A形成了循環(huán)依賴自己依賴自己。
2、如何解決循環(huán)依賴問題?
循環(huán)依賴本身沒有問題,問題是Spring中加入了依賴注入機制,也就是自動給屬性賦值。當創(chuàng)建Bean實例化以后,需要給Bean中需要賦值的屬性全部自動賦值才能交給用戶使用。但如果是循環(huán)依賴的情況,以兩個Bean相互依賴的情況為例,
假設Bean A已經(jīng)實例化,但是Bean A中需要自動賦值Bean B并沒有初始化,但如果Spring立刻去初始化Bean B,發(fā)現(xiàn)Bean B中需要自動賦值的Bean A沒有初始化,如果這樣相互等待,就會形成死循環(huán),最終,有可能導致Spring容器都無法啟動。
就好比,我們以前讀書的時候,老師經(jīng)常教我們一個考試方法,就是遇到難題不會答的時候,不要死磕,要繼續(xù)往下做其他的題。否則,會因為一道難題卡住影響到整個的答題進度,還會影響正常的發(fā)揮,影響考試結果。
那這個問題該怎么解決呢?使用緩存。
就是將所有實例化好的Bean,全部放到一個容器中緩存起來,并且將已經(jīng)完成實例化但沒有完成賦值的,打上標記。
然后,等Bean全部實例化以后,再重新掃描一遍容器,將沒有完成賦值的Bean屬性完成賦值,這個時候,所有未完成賦值的Bean都已經(jīng)能夠找到對應的實例了。
那么問題來了。解決循環(huán)依賴問題,一定要二級緩存嗎?答案是不一定。但是Spring中為什么又要設計二級緩存呢?
這時候,我們可以這樣理解,假設,我們只有一個緩存容器,并且緩存是直接開放給用戶可以調(diào)用的,如果將未完成賦值的Bean和已完成賦值的Bean全部放到同一個容器,那這個時候,調(diào)用者就有可能拿到未賦值的Bean,這樣的Bean對于用戶來說是不可用的,可能會導致空指針異常。
所以,Spring設計者,才有了這樣一個設計,將能夠直接提供給用戶使用的Bean放到一級緩存中,這樣Bean稱之為終態(tài)Bean,或者叫成熟Bean。
將已經(jīng)完成初始化,但還不能提供給用戶使用的Bean單獨放到一個緩存容器中,就是二級緩存,這樣的Bean稱之為臨時Bean,或者叫早期Bean。
依照以上的分析,理論上二級緩存就能解決循環(huán)依賴問題,那為什么Spring還要設計一個三級緩存呢?
3、如何理解三級緩存?
我們都知道,Spring中有很多注入的Bean是需要創(chuàng)建代理Bean的,但是,不是所有的Bean都需要再實例化之后立馬就會創(chuàng)建代理Bean。是要等到Bean初始化全部完成之后才創(chuàng)建代理Bean。因此,循環(huán)依賴的出現(xiàn),Spring又不得不去提前創(chuàng)建代理Bean。如果不創(chuàng)建代理Bean,注入原始Bean就會產(chǎn)生錯誤。因 此,Spring設計三級緩存,專門用來存放代理Bean。但是,創(chuàng)建代理Bean的又不同的規(guī)則,因此,Spring三級緩存中,并不是直接保存代理Bean的引用,而是保存創(chuàng)建代理Bean的Factory。
4、總結
所以,總結結論為,單純解決循環(huán)依賴可以只用二級緩存,但是如果涉及到代理對象的循環(huán)依賴,就需要用到三級緩存。其實一、二、三級緩存是根據(jù)獲取對象的順序來命名的,我們完全可以這樣理解,一級緩存就是終態(tài)緩存,二級緩存是臨時緩存、三級緩存是代理工廠的緩存。
這張圖完整地描述了一、二、三級緩存的運行邏輯。