淺析Spring中的循環(huán)依賴問題
Spring的循環(huán)依賴
本文不會(huì)詳細(xì)講解 Spring 循環(huán)依賴的基礎(chǔ)問題。
我相信能閱讀到本文的,對(duì) Spring 循環(huán)依賴已經(jīng)有一定了解,但可能存在一些疑惑。本文就是嘗試來解決這些疑惑的。
我們都知道 Spring 是利用了 三級(jí)緩存 來解決循環(huán)依賴的,其實(shí)現(xiàn)本質(zhì)是通過提前暴露已經(jīng)實(shí)例化但尚未初始化的 bean 來完成的。
但是呢,我們?nèi)匀粫?huì)想,這里為什么要使用三級(jí)緩存?而且,我相信,不少人都曾手寫過代碼來解決循環(huán)依賴的問題,那時(shí)候,他們也只用了二級(jí)緩存,參考下圖:

我們可以仔細(xì)跟蹤序號(hào),理清整個(gè)流程。所以,二級(jí)緩存是能夠解決循環(huán)依賴,這也符合它的本質(zhì):“提前暴露對(duì)象”。這個(gè)流程圖并沒有描述接下來的流程,這里使用文字簡單描述下:
- 對(duì)象A獲取到已創(chuàng)建完成的對(duì)象B注入;
- 對(duì)象A完成字段注入以及初始化,并放入一級(jí)緩存;
- 對(duì)象A從二級(jí)緩存中移除;
既然二級(jí)緩存能夠解決循環(huán)依賴了,那為什么要使用三級(jí)緩存呢?網(wǎng)上的說法是,那是因?yàn)?Spring 中存在替換注入對(duì)象的問題。通俗地來說就是:“一個(gè)半成品對(duì)象有可能在被對(duì)象b注入以后,被更改為其它的實(shí)例對(duì)象,那么對(duì)象b注入的就是一個(gè)過期的對(duì)象了”。
這種情況會(huì)導(dǎo)致對(duì)象b注入了一個(gè)并不存在于容器中的對(duì)象A(因?yàn)楸桓暮蟮膶?duì)象注入了容器,替換掉了原來的對(duì)象)。所以,大多數(shù)人會(huì)認(rèn)為三級(jí)緩存是為了解決這個(gè)問題的,讓我們來看看真的是如此嘛?上代碼:
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
@Component
public class ResetServiceABeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(beanName.equals("serviceA")){
return new ServiceA();
}
return bean;
}
}
ServiceA 和 ServiceB 互相依賴,ResetServiceABeanPostProcessor 則是為了在ServiceB 注入了原來的ServiceA 后,將原來的 ServiceA 給替換掉。我們模擬了上述場景,但最后的運(yùn)行結(jié)果卻是得到了一個(gè) BeanCurrentlyInCreationException 異常,異常在圖中的 620 行拋出。

可以發(fā)現(xiàn),似乎它并沒有解決這個(gè)“注入了過期對(duì)象”的問題,可是它至少檢測出了這個(gè)問題。所以,我個(gè)人認(rèn)為,三級(jí)緩存并不是來解決這個(gè)問題,而是來在啟動(dòng)時(shí)檢測這個(gè)問題的。
文章寫到這里似乎也差不多了,但我還想糾正一點(diǎn),網(wǎng)上有很多對(duì)于三級(jí)緩存的描述如下:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
...
// 從上至下 分表代表這“三級(jí)緩存”
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一級(jí)緩存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二級(jí)緩存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三級(jí)緩存
...
/** Names of beans that are currently in creation. */
// 這個(gè)緩存也十分重要:它表示bean創(chuàng)建過程中都會(huì)在里面呆著~
// 它在Bean開始創(chuàng)建時(shí)放值,創(chuàng)建完成時(shí)會(huì)將其移出~
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/** Names of beans that have already been created at least once. */
// 當(dāng)這個(gè)Bean被創(chuàng)建完成后,會(huì)標(biāo)記為這個(gè) 注意:這里是set集合 不會(huì)重復(fù)
// 至少被創(chuàng)建了一次的 都會(huì)放進(jìn)這里~~~~
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
}
但源碼中的順序卻不是如此(我使用的是 5.1.5 版本):
/** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
但我想三級(jí)緩存的分層并不是依賴上面的源碼順序來分為一二三的,而應(yīng)該是根據(jù)從緩存中獲取對(duì)象的順序來分層的:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
最后,我結(jié)合上述的場景來分析下這三個(gè)“緩存”中元素的變化(省略了其它無關(guān)流程):

紅色線條代表取出元素,虛線代表最終將不存在。
這里做下總結(jié):
二級(jí)緩存也是能解決循環(huán)依賴的,使用三級(jí)緩存是為了幫助檢測提前暴露的對(duì)象在后期被修改的這種情況;
通過 earlySingletonObjects 持有被暴露的對(duì)象,然后在最終返回對(duì)象時(shí)進(jìn)行比對(duì)。如果不是同一個(gè)對(duì)象,則代表發(fā)生了對(duì)象后期被修改的情況。
到此這篇關(guān)于淺析Spring中的循環(huán)依賴問題的文章就介紹到這了,更多相關(guān)Spring的循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea中@override報(bào)錯(cuò)在實(shí)現(xiàn)接口方法時(shí)無法使用的解決
文章介紹了在使用IDEA新建Filter類時(shí)遇到的@Override注解報(bào)錯(cuò)問題,原因是JDK版本過低,通過在maven配置文件中設(shè)置編譯插件或更改項(xiàng)目JDK版本,解決了該問題2025-12-12
Spring?AI?Alibaba框架構(gòu)建智能體Agent的完整指南
隨著大語言模型(LLM)技術(shù)的快速發(fā)展,構(gòu)建智能Agent應(yīng)用變得越來越簡單,本文將通過兩個(gè)實(shí)際的代碼示例,展示如何使用Spring?AI?Alibaba框架構(gòu)建功能豐富的天氣查詢Agent,感興趣的小伙伴可以了解下2026-03-03
Java實(shí)現(xiàn)的Excel列號(hào)數(shù)字與字母互相轉(zhuǎn)換功能
這篇文章主要介紹了Java實(shí)現(xiàn)的Excel列號(hào)數(shù)字與字母互相轉(zhuǎn)換功能,涉及java針對(duì)Excel相關(guān)數(shù)值與字符串操作技巧,需要的朋友可以參考下2018-03-03
Java深入了解數(shù)據(jù)結(jié)構(gòu)中常見的排序算法
這篇文章主要介紹了Java常用的排序算法及代碼實(shí)現(xiàn),在Java開發(fā)中,對(duì)排序的應(yīng)用需要熟練的掌握,這樣才能夠確保Java學(xué)習(xí)時(shí)候能夠有扎實(shí)的基礎(chǔ)能力。那Java有哪些排序算法呢?本文小編就來詳細(xì)說說Java常見的排序算法,需要的朋友可以參考一下2022-01-01
Java 中間件Dubbo 服務(wù)降級(jí)以及Mock 機(jī)制詳解
本文將深入探討Dubbo的Mock機(jī)制,從原理、配置方式、使用場景到實(shí)戰(zhàn)案例,全面解析如何利用這一特性構(gòu)建高可用的微服務(wù)系統(tǒng),感興趣的朋友跟隨小編一起看看吧2026-02-02
redisson特性及優(yōu)雅實(shí)現(xiàn)示例
這篇文章主要為大家介紹了redisson特性及優(yōu)雅實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11

