Java中通過三級(jí)緩存解決Spring循環(huán)依賴詳解
1. 循環(huán)依賴
1.1 什么是循環(huán)依賴
首先,什么是循環(huán)依賴?這個(gè)其實(shí)好理解,就是兩個(gè) Bean 互相依賴,類似下面這樣:
@Service
public class AService {
@Autowired
BService bService;
}
@Service
public class BService {
@Autowired
AService aService;
}AService 和 BService 互相依賴:

這個(gè)應(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)依賴就直接報(bào)錯(cuò)了。
另外,Spring 并不能處理所有的循環(huán)依賴,后面松哥會(huì)和大家進(jìn)行分析。
2. 循環(huán)依賴解決思路
2.1 解決思路
那么對(duì)于循環(huán)依賴該如何解決呢?其實(shí)很簡單,中加加入一個(gè)緩存就可以了,小伙伴們來看下面這張圖:

我們在這里引入了一個(gè)緩存池。
當(dāng)我們需要?jiǎng)?chuàng)建 AService 的實(shí)例的時(shí)候,會(huì)首先通過 Java 反射創(chuàng)建出來一個(gè)原始的 AService,這個(gè)原始 AService 可以簡單理解為剛剛 new 出來(實(shí)際是剛剛通過反射創(chuàng)建出來)還沒設(shè)置任何屬性的 AService,此時(shí),我們把這個(gè) AService 先存入到一個(gè)緩存池中。
接下來我們就需要給 AService 的屬性設(shè)置值了,同時(shí)還要處理 AService 的依賴,這時(shí)我們發(fā)現(xiàn) AService 依賴 BService,那么就去創(chuàng)建 BService 對(duì)象,結(jié)果創(chuàng)建 BService 的時(shí)候,發(fā)現(xiàn) BService 依賴 AService,那么此時(shí)就先從緩存池中取出來 AService 先用著,然后繼續(xù) BService 創(chuàng)建的后續(xù)流程,直到 BService 創(chuàng)建完成后,將之賦值給 AService,此時(shí) AService 和 BService 就都創(chuàng)建完成了。
可能有小伙伴會(huì)說,BService 從緩存池中拿到的 AService 是一個(gè)半成品,并不是真正的最終的 AService,但是小伙伴們要知道,咱們 Java 是引用傳遞(也可以認(rèn)為是值傳遞,只不過這個(gè)值是內(nèi)存地址),BService 當(dāng)時(shí)拿到的是 AService 的引用,說白了就是一塊內(nèi)存地址而已,根據(jù)這個(gè)地址找到的就是 AService,所以,后續(xù)如果 AService 創(chuàng)建完成后,BService 所拿到的 AService 就是完整的 AService 了。
那么上面提到的這個(gè)緩存池,在 Spring 容器中有一個(gè)專門的名字,就叫做 earlySingletonObjects,這是 Spring 三級(jí)緩存中的二級(jí)緩存,這里保存的是剛剛通過反射創(chuàng)建出來的 Bean,這些 Bean 還沒有經(jīng)歷過完整生命周期,Bean 的屬性可能都還沒有設(shè)置,Bean 需要的依賴都還沒有注入進(jìn)來。另外兩級(jí)緩存分別是:
- singletonObjects:這是一級(jí)緩存,一級(jí)緩存中保存的是所有經(jīng)歷了完整生命周期的 Bean,即一個(gè) Bean 從創(chuàng)建、到屬性賦值、到各種處理器的執(zhí)行等等,都經(jīng)歷過了,就存到 singletonObjects 中,當(dāng)我們需要獲取一個(gè) Bean 的時(shí)候,首先會(huì)去一級(jí)緩存中查找,當(dāng)一級(jí)緩存中沒有的時(shí)候,才會(huì)考慮去二級(jí)緩存。
- singletonFactories:這是三級(jí)緩存。在一級(jí)緩存和二級(jí)緩存中,緩存的 key 是 beanName,緩存的 value 則是一個(gè) Bean 對(duì)象,但是在三級(jí)緩存中,緩存的 value 是一個(gè) Lambda 表達(dá)式,通過這個(gè) Lambda 表達(dá)式可以創(chuàng)建出來目標(biāo)對(duì)象的一個(gè)代理對(duì)象。
有的小伙伴可能會(huì)覺得奇怪,按照上文的介紹,一級(jí)緩存和二級(jí)緩存就足以解決循環(huán)依賴了,為什么還冒出來一個(gè)三級(jí)緩存?那就得考慮 AOP 的情況了!
2.2 存在 AOP 怎么辦
上面松哥給大家介紹的是普通的 Bean 創(chuàng)建,那確實(shí)沒有問題。但是 Spring 中還有一個(gè)非常重要的能力,那就是 AOP。
說到這里,我得先和小伙伴么說一說 Spring 中 AOP 的創(chuàng)建流程。
正常來說是我們首先通過反射獲取到一個(gè) Bean 的實(shí)例,然后就是給這個(gè) Bean 填充屬性,屬性填充完畢之后,接下來就是執(zhí)行各種 BeanPostProcessor 了,如果這個(gè) Bean 中有需要代理的方法,那么系統(tǒng)就會(huì)自動(dòng)配置對(duì)應(yīng)的后置處理器,松哥舉一個(gè)簡單例子,假設(shè)我有如下一個(gè) Service:
@Service
public class UserService {
@Async
public void hello() {
System.out.println("hello>>>"+Thread.currentThread().getName());
}
}那么系統(tǒng)就會(huì)自動(dòng)提供一個(gè)名為 AsyncAnnotationBeanPostProcessor 的處理器,在這個(gè)處理器中,系統(tǒng)會(huì)生成一個(gè)代理的 UserService 對(duì)象,并用這個(gè)對(duì)象代替原本的 UserService。
那么小伙伴們要搞清楚的是,原本的 UserService 和新生成的代理的 UserService 是兩個(gè)不同的對(duì)象,占兩塊不同的內(nèi)存地址?。?!
我們再來回顧下面這張圖:

如果 AService 最終是要生成一個(gè)代理對(duì)象的話,那么 AService 存到緩存池的其實(shí)還是原本的 AService,因?yàn)榇藭r(shí)還沒到處理 AOP 那一步(要先給各個(gè)屬性賦值,然后才是 AOP 處理),這就導(dǎo)致 BService 從緩存池里拿到的 AService 是原本的 AService,等到 BService 創(chuàng)建完畢之后,AService 的屬性賦值才完成,接下來在 AService 后續(xù)的創(chuàng)建流程中,AService 會(huì)變成了一個(gè)代理對(duì)象了,不是緩存池里的 AService 了,最終就導(dǎo)致 BService 所依賴的 AService 和最終創(chuàng)建出來的 AService 不是同一個(gè)。
為了解決這個(gè)問題,Spring 引入了三級(jí)緩存 singletonFactories。
singletonFactories 的工作機(jī)制是這樣的(假設(shè) AService 最終是一個(gè)代理對(duì)象):
當(dāng)我們創(chuàng)建一個(gè) AService 的時(shí)候,通過反射剛把原始的 AService 創(chuàng)建出來之后,先去判斷當(dāng)前一級(jí)緩存中是否存在當(dāng)前 Bean,如果不存在,則:
- 首先向三級(jí)緩存中添加一條記錄,記錄的 key 就是當(dāng)前 Bean 的 beanName,value 則是一個(gè) Lambda 表達(dá)式 ObjectFactory,通過執(zhí)行這個(gè) Lambda 可以給當(dāng)前 AService 生成代理對(duì)象。
- 然后如果二級(jí)緩存中存在當(dāng)前 AService Bean,則移除掉。
現(xiàn)在繼續(xù)去給 AService 各個(gè)屬性賦值,結(jié)果發(fā)現(xiàn) AService 需要 BService,然后就去創(chuàng)建 BService,創(chuàng)建 BService 的時(shí)候,發(fā)現(xiàn) BService 又需要用到 AService,于是就先去一級(jí)緩存中查找是否有 AService,如果有,就使用,如果沒有,則去二級(jí)緩存中查找是否有 AService,如果有,就使用,如果沒有,則去三級(jí)緩存中找出來那個(gè) ObjectFactory,然后執(zhí)行這里的 getObject 方法,這個(gè)方法在執(zhí)行的過程中,會(huì)去判斷是否需要生成一個(gè)代理對(duì)象,如果需要就生成代理對(duì)象返回,如果不需要生成代理對(duì)象,則將原始對(duì)象返回即可。最后,把拿到手的對(duì)象存入到二級(jí)緩存中以備下次使用,同時(shí)刪除掉三級(jí)緩存中對(duì)應(yīng)的數(shù)據(jù)。這樣 AService 所依賴的 BService 就創(chuàng)建好了。
接下來繼續(xù)去完善 AService,去執(zhí)行各種后置的處理器,此時(shí),有的后置處理器想給 AService 生成代理對(duì)象,發(fā)現(xiàn) AService 已經(jīng)是代理對(duì)象了,就不用生成了,直接用已有的代理對(duì)象去代替 AService 即可。
至此,AService 和 BService 都搞定。
本質(zhì)上,singletonFactories 是把 AOP 的過程提前了。
3. 小結(jié)
總的來說,Spring 解決循環(huán)依賴把握住兩個(gè)關(guān)鍵點(diǎn):
- 提前暴露:剛剛創(chuàng)建好的對(duì)象還沒有進(jìn)行任何賦值的時(shí)候,將之暴露出來放到緩存中,供其他 Bean 提前引用(二級(jí)緩存)。
- 提前 AOP:A 依賴 B 的時(shí)候,去檢查是否發(fā)生了循環(huán)依賴(檢查的方式就是將正在創(chuàng)建的 A 標(biāo)記出來,然后 B 需要 A,B 去創(chuàng)建 A 的時(shí)候,發(fā)現(xiàn) A 正在創(chuàng)建,就說明發(fā)生了循環(huán)依賴),如果發(fā)生了循環(huán)依賴,就提前進(jìn)行 AOP 處理,處理完成后再使用(三級(jí)緩存)。
原本 AOP 這個(gè)過程是屬性賦完值之后,再由各種后置處理器去處理 AOP 的( AbstractAutoProxyCreator ),但是如果發(fā)生了循環(huán)依賴,就先 AOP,然后屬性賦值,最后等到后置處理器執(zhí)行的時(shí)候,就不再做 AOP 的處理了。
不過需要注意,三級(jí)緩存并不能解決所有的循環(huán)依賴
到此這篇關(guān)于Java中通過三級(jí)緩存解決Spring循環(huán)依賴詳解的文章就介紹到這了,更多相關(guān)三級(jí)緩存解決Spring循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java操作PDF文件實(shí)現(xiàn)簽訂電子合同詳細(xì)教程
這篇文章主要介紹了如何在PDF中加入電子簽章與電子簽名的過程,包括編寫Word文件、生成PDF、為PDF格式做表單、為表單賦值、生成文檔以及上傳到OBS中的步驟,需要的朋友可以參考下2025-01-01
使用Java Stream將集合轉(zhuǎn)換為一對(duì)一Map的方法詳解
在日常的開發(fā)工作中,我們經(jīng)常使用到Java Stream,特別是Stream API中提供的Collectors.toList()收集器,但有些場景下,我們需要將集合轉(zhuǎn)換為Map,所以本文就給大家介紹了如何使用Java Stream將集合轉(zhuǎn)換為一對(duì)一Map,需要的朋友可以參考下2025-12-12
SpringBoot配置@Configuration注解和@bean注解
這篇文章主要介紹了SpringBoot配置@Configuration注解和@bean注解,文章圍繞主題相關(guān)內(nèi)容展開詳細(xì)介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-04-04
springboot集成nacos讀取nacos配置數(shù)據(jù)的原理
這篇文章主要介紹了springboot集成nacos讀取nacos配置數(shù)據(jù)的原理,文中有詳細(xì)的代碼流程,對(duì)大家學(xué)習(xí)springboot集成nacos有一定的幫助,需要的朋友可以參考下2023-05-05
SpringMVC Controller 返回值的可選類型詳解
本篇文章主要介紹了SpringMVC Controller 返回值的可選類型詳解 ,spring mvc 支持如下的返回方式:ModelAndView, Model, ModelMap, Map,View, String, void,有興趣的可以了解一下2017-05-05

