Java中通過三級緩存解決Spring循環(huán)依賴詳解
1. 循環(huán)依賴
1.1 什么是循環(huán)依賴
首先,什么是循環(huán)依賴?這個其實(shí)好理解,就是兩個 Bean 互相依賴,類似下面這樣:
@Service public class AService { @Autowired BService bService; } @Service public class BService { @Autowired AService aService; }
AService 和 BService 互相依賴:
這個應(yīng)該很好理解。
1.2 循環(huán)依賴的類型
一般來說,循環(huán)依賴有三種不同的形態(tài),上面 1.1 小節(jié)是其中一種。
另外兩種分別是三者依賴,如下圖:
這種循環(huán)依賴一般隱藏比較深,不易發(fā)覺。
還有自我依賴,如下圖:
一般來說,如果我們的代碼中出現(xiàn)了循環(huán)依賴,則說明我們的代碼在設(shè)計(jì)的過程中可能存在問題,我們應(yīng)該盡量避免循環(huán)依賴的發(fā)生。不過一旦發(fā)生了循環(huán)依賴,Spring 默認(rèn)也幫我們處理好了,當(dāng)然這并不能說明循環(huán)依賴這種代碼就沒問題。實(shí)際上在目前最新版的 Spring 中,循環(huán)依賴是要額外開啟的,如果不額外配置,發(fā)生了循環(huán)依賴就直接報錯了。
另外,Spring 并不能處理所有的循環(huán)依賴,后面松哥會和大家進(jìn)行分析。
2. 循環(huán)依賴解決思路
2.1 解決思路
那么對于循環(huán)依賴該如何解決呢?其實(shí)很簡單,中加加入一個緩存就可以了,小伙伴們來看下面這張圖:
我們在這里引入了一個緩存池。
當(dāng)我們需要創(chuàng)建 AService 的實(shí)例的時候,會首先通過 Java 反射創(chuàng)建出來一個原始的 AService,這個原始 AService 可以簡單理解為剛剛 new 出來(實(shí)際是剛剛通過反射創(chuàng)建出來)還沒設(shè)置任何屬性的 AService,此時,我們把這個 AService 先存入到一個緩存池中。
接下來我們就需要給 AService 的屬性設(shè)置值了,同時還要處理 AService 的依賴,這時我們發(fā)現(xiàn) AService 依賴 BService,那么就去創(chuàng)建 BService 對象,結(jié)果創(chuàng)建 BService 的時候,發(fā)現(xiàn) BService 依賴 AService,那么此時就先從緩存池中取出來 AService 先用著,然后繼續(xù) BService 創(chuàng)建的后續(xù)流程,直到 BService 創(chuàng)建完成后,將之賦值給 AService,此時 AService 和 BService 就都創(chuàng)建完成了。
可能有小伙伴會說,BService 從緩存池中拿到的 AService 是一個半成品,并不是真正的最終的 AService,但是小伙伴們要知道,咱們 Java 是引用傳遞(也可以認(rèn)為是值傳遞,只不過這個值是內(nèi)存地址),BService 當(dāng)時拿到的是 AService 的引用,說白了就是一塊內(nèi)存地址而已,根據(jù)這個地址找到的就是 AService,所以,后續(xù)如果 AService 創(chuàng)建完成后,BService 所拿到的 AService 就是完整的 AService 了。
那么上面提到的這個緩存池,在 Spring 容器中有一個專門的名字,就叫做 earlySingletonObjects,這是 Spring 三級緩存中的二級緩存,這里保存的是剛剛通過反射創(chuàng)建出來的 Bean,這些 Bean 還沒有經(jīng)歷過完整生命周期,Bean 的屬性可能都還沒有設(shè)置,Bean 需要的依賴都還沒有注入進(jìn)來。另外兩級緩存分別是:
- singletonObjects:這是一級緩存,一級緩存中保存的是所有經(jīng)歷了完整生命周期的 Bean,即一個 Bean 從創(chuàng)建、到屬性賦值、到各種處理器的執(zhí)行等等,都經(jīng)歷過了,就存到 singletonObjects 中,當(dāng)我們需要獲取一個 Bean 的時候,首先會去一級緩存中查找,當(dāng)一級緩存中沒有的時候,才會考慮去二級緩存。
- singletonFactories:這是三級緩存。在一級緩存和二級緩存中,緩存的 key 是 beanName,緩存的 value 則是一個 Bean 對象,但是在三級緩存中,緩存的 value 是一個 Lambda 表達(dá)式,通過這個 Lambda 表達(dá)式可以創(chuàng)建出來目標(biāo)對象的一個代理對象。
有的小伙伴可能會覺得奇怪,按照上文的介紹,一級緩存和二級緩存就足以解決循環(huán)依賴了,為什么還冒出來一個三級緩存?那就得考慮 AOP 的情況了!
2.2 存在 AOP 怎么辦
上面松哥給大家介紹的是普通的 Bean 創(chuàng)建,那確實(shí)沒有問題。但是 Spring 中還有一個非常重要的能力,那就是 AOP。
說到這里,我得先和小伙伴么說一說 Spring 中 AOP 的創(chuàng)建流程。
正常來說是我們首先通過反射獲取到一個 Bean 的實(shí)例,然后就是給這個 Bean 填充屬性,屬性填充完畢之后,接下來就是執(zhí)行各種 BeanPostProcessor 了,如果這個 Bean 中有需要代理的方法,那么系統(tǒng)就會自動配置對應(yīng)的后置處理器,松哥舉一個簡單例子,假設(shè)我有如下一個 Service:
@Service public class UserService { @Async public void hello() { System.out.println("hello>>>"+Thread.currentThread().getName()); } }
那么系統(tǒng)就會自動提供一個名為 AsyncAnnotationBeanPostProcessor 的處理器,在這個處理器中,系統(tǒng)會生成一個代理的 UserService 對象,并用這個對象代替原本的 UserService。
那么小伙伴們要搞清楚的是,原本的 UserService 和新生成的代理的 UserService 是兩個不同的對象,占兩塊不同的內(nèi)存地址?。?!
我們再來回顧下面這張圖:
如果 AService 最終是要生成一個代理對象的話,那么 AService 存到緩存池的其實(shí)還是原本的 AService,因?yàn)榇藭r還沒到處理 AOP 那一步(要先給各個屬性賦值,然后才是 AOP 處理),這就導(dǎo)致 BService 從緩存池里拿到的 AService 是原本的 AService,等到 BService 創(chuàng)建完畢之后,AService 的屬性賦值才完成,接下來在 AService 后續(xù)的創(chuàng)建流程中,AService 會變成了一個代理對象了,不是緩存池里的 AService 了,最終就導(dǎo)致 BService 所依賴的 AService 和最終創(chuàng)建出來的 AService 不是同一個。
為了解決這個問題,Spring 引入了三級緩存 singletonFactories。
singletonFactories 的工作機(jī)制是這樣的(假設(shè) AService 最終是一個代理對象):
當(dāng)我們創(chuàng)建一個 AService 的時候,通過反射剛把原始的 AService 創(chuàng)建出來之后,先去判斷當(dāng)前一級緩存中是否存在當(dāng)前 Bean,如果不存在,則:
- 首先向三級緩存中添加一條記錄,記錄的 key 就是當(dāng)前 Bean 的 beanName,value 則是一個 Lambda 表達(dá)式 ObjectFactory,通過執(zhí)行這個 Lambda 可以給當(dāng)前 AService 生成代理對象。
- 然后如果二級緩存中存在當(dāng)前 AService Bean,則移除掉。
現(xiàn)在繼續(xù)去給 AService 各個屬性賦值,結(jié)果發(fā)現(xiàn) AService 需要 BService,然后就去創(chuàng)建 BService,創(chuàng)建 BService 的時候,發(fā)現(xiàn) BService 又需要用到 AService,于是就先去一級緩存中查找是否有 AService,如果有,就使用,如果沒有,則去二級緩存中查找是否有 AService,如果有,就使用,如果沒有,則去三級緩存中找出來那個 ObjectFactory,然后執(zhí)行這里的 getObject 方法,這個方法在執(zhí)行的過程中,會去判斷是否需要生成一個代理對象,如果需要就生成代理對象返回,如果不需要生成代理對象,則將原始對象返回即可。最后,把拿到手的對象存入到二級緩存中以備下次使用,同時刪除掉三級緩存中對應(yīng)的數(shù)據(jù)。這樣 AService 所依賴的 BService 就創(chuàng)建好了。
接下來繼續(xù)去完善 AService,去執(zhí)行各種后置的處理器,此時,有的后置處理器想給 AService 生成代理對象,發(fā)現(xiàn) AService 已經(jīng)是代理對象了,就不用生成了,直接用已有的代理對象去代替 AService 即可。
至此,AService 和 BService 都搞定。
本質(zhì)上,singletonFactories 是把 AOP 的過程提前了。
3. 小結(jié)
總的來說,Spring 解決循環(huán)依賴把握住兩個關(guān)鍵點(diǎn):
- 提前暴露:剛剛創(chuàng)建好的對象還沒有進(jìn)行任何賦值的時候,將之暴露出來放到緩存中,供其他 Bean 提前引用(二級緩存)。
- 提前 AOP:A 依賴 B 的時候,去檢查是否發(fā)生了循環(huán)依賴(檢查的方式就是將正在創(chuàng)建的 A 標(biāo)記出來,然后 B 需要 A,B 去創(chuàng)建 A 的時候,發(fā)現(xiàn) A 正在創(chuàng)建,就說明發(fā)生了循環(huán)依賴),如果發(fā)生了循環(huán)依賴,就提前進(jìn)行 AOP 處理,處理完成后再使用(三級緩存)。
原本 AOP 這個過程是屬性賦完值之后,再由各種后置處理器去處理 AOP 的( AbstractAutoProxyCreator ),但是如果發(fā)生了循環(huán)依賴,就先 AOP,然后屬性賦值,最后等到后置處理器執(zhí)行的時候,就不再做 AOP 的處理了。
不過需要注意,三級緩存并不能解決所有的循環(huán)依賴
到此這篇關(guān)于Java中通過三級緩存解決Spring循環(huán)依賴詳解的文章就介紹到這了,更多相關(guān)三級緩存解決Spring循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java利用TCP協(xié)議實(shí)現(xiàn)客戶端與服務(wù)器通信(附通信源碼)
這篇文章主要介紹了Java利用TCP協(xié)議實(shí)現(xiàn)客戶端與服務(wù)器通信(附通信源碼),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07SpringBoot項(xiàng)目加入沖突動態(tài)監(jiān)測算法的實(shí)現(xiàn)
沖突動態(tài)監(jiān)測算法是一種網(wǎng)絡(luò)通信中的沖突檢測方法,適用于無線網(wǎng)絡(luò)或其他共享傳輸介質(zhì)的環(huán)境,本文主要介紹了SpringBoot項(xiàng)目加入沖突動態(tài)監(jiān)測算法的實(shí)現(xiàn),感興趣的可以了解一下2023-09-09springboot整合阿里云百煉DeepSeek實(shí)現(xiàn)sse流式打印的操作方法
這篇文章主要介紹了springboot整合阿里云百煉DeepSeek實(shí)現(xiàn)sse流式打印,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2025-04-04java實(shí)現(xiàn)后臺返回base64圖形編碼
這篇文章主要介紹了java實(shí)現(xiàn)后臺返回base64圖形編碼,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06IDEA+Maven搭建JavaWeb項(xiàng)目的方法步驟
本文主要介紹了IDEA+Maven搭建JavaWeb項(xiàng)目的方法步驟,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11Java漢字轉(zhuǎn)拼音工具類完整代碼實(shí)例
這篇文章主要介紹了java漢字轉(zhuǎn)拼音工具類完整代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03IDEA編譯報錯:Error:java:無效的源發(fā)行版:17的解決辦法
IDEA里面裝了幾個版本的JDK,導(dǎo)入工程后時不時提示一下錯誤,下面這篇文章主要給大家介紹了關(guān)于IDEA編譯報錯:Error:java:無效的源發(fā)行版:17的解決辦法,需要的朋友可以參考下2023-01-01