Spring中循環(huán)依賴問題的解決機(jī)制及詳細(xì)流程
一、解決機(jī)制
1. 什么是循環(huán)依賴
循環(huán)依賴是指兩個(gè)或多個(gè)Bean之間相互依賴對方,形成一個(gè)閉環(huán)的依賴關(guān)系。最常見的情況是當(dāng)Bean A依賴Bean B,而Bean B又依賴Bean A時(shí),就形成了循環(huán)依賴。在Spring容器初始化過程中,如果不加以特殊處理,這種循環(huán)依賴會導(dǎo)致Bean的創(chuàng)建過程陷入死循環(huán),最終導(dǎo)致應(yīng)用啟動失敗。
例如以下代碼展示了一個(gè)典型的循環(huán)依賴場景:
@Service
public class A {
@Autowired
private B b;
}
@Service
public class B {
@Autowired
private A a;
}2. Spring三級緩存機(jī)制概述
Spring框架通過巧妙的"三級緩存"機(jī)制解決了循環(huán)依賴問題。這三級緩存在Spring源碼中是通過三個(gè)Map集合實(shí)現(xiàn)的:
// 一級緩存:存放完全初始化好的Bean private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二級緩存:存放原始的Bean對象(尚未填充屬性),用于解決循環(huán)依賴 private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三級緩存:存放Bean工廠對象,用于解決循環(huán)依賴 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
這三級緩存的作用分別是:
- 一級緩存(singletonObjects):用于存儲完全初始化好的Bean,即已經(jīng)完成實(shí)例化、屬性填充和初始化的Bean,可以直接被其他對象使用。
- 二級緩存(earlySingletonObjects):用于存儲早期曝光的Bean對象,這些Bean已經(jīng)完成實(shí)例化但還未完成屬性填充和初始化。當(dāng)出現(xiàn)循環(huán)依賴時(shí),其他Bean可以引用這個(gè)未完全初始化的Bean。
- 三級緩存(singletonFactories):用于存儲Bean的工廠對象,主要用于處理需要AOP代理的Bean的循環(huán)依賴問題。它存儲的是一個(gè)Lambda表達(dá)式,這個(gè)表達(dá)式會返回Bean的早期引用,并在需要時(shí)將其放入二級緩存。
3. 三級緩存的查找順序
當(dāng)Spring需要獲取一個(gè)單例Bean時(shí),會按照以下順序查找:
- 首先從一級緩存(singletonObjects)中查找,如果找到直接返回
- 如果一級緩存沒有,再從二級緩存(earlySingletonObjects)中查找
- 如果二級緩存也沒有,則從三級緩存(singletonFactories)中查找對應(yīng)的工廠,如果找到則通過工廠獲取對象,并將其放入二級緩存,同時(shí)從三級緩存中移除
這種層級查找的機(jī)制確保了在循環(huán)依賴的情況下,Bean能夠被正確地創(chuàng)建和注入。
4. Spring解決循環(huán)依賴的詳細(xì)流程
以A、B兩個(gè)類相互依賴為例,Spring解決循環(huán)依賴的流程如下:
- 創(chuàng)建Bean A:
- Spring首先創(chuàng)建Bean A的實(shí)例(僅完成實(shí)例化,未進(jìn)行屬性填充)
- 將A的創(chuàng)建工廠放入三級緩存singletonFactories中
- 開始給A填充屬性,發(fā)現(xiàn)依賴了B
- 創(chuàng)建Bean B:
- Spring開始創(chuàng)建B(因?yàn)锳依賴B)
- 實(shí)例化B,并將B的創(chuàng)建工廠放入三級緩存
- 開始給B填充屬性,發(fā)現(xiàn)依賴了A
- 處理循環(huán)依賴:
- 此時(shí)需要注入A,但A正在創(chuàng)建中
- Spring嘗試從一級緩存查找A,未找到
- 繼續(xù)從二級緩存查找A,未找到
- 最后從三級緩存中找到A的工廠對象
- 通過工廠獲取A的早期引用(可能是原始對象,也可能是代理對象)
- 將A的早期引用放入二級緩存,并從三級緩存中移除A的工廠
- 將A的早期引用注入到B中
- 完成Bean創(chuàng)建:
- B完成屬性填充和初始化,放入一級緩存
- 返回到A的屬性填充流程,將B注入到A中
- A完成屬性填充和初始化,放入一級緩存
通過這個(gè)流程,Spring成功解決了循環(huán)依賴問題,關(guān)鍵在于提前暴露了Bean的早期引用。
5. 為什么需要三級緩存
很多人會疑惑,為什么需要三級緩存?二級緩存不能解決循環(huán)依賴問題嗎?
實(shí)際上,在不考慮AOP的情況下,二級緩存確實(shí)可以解決循環(huán)依賴問題。但Spring設(shè)計(jì)三級緩存的主要目的是為了處理AOP代理的情況。
在Spring中,AOP代理是在Bean生命周期的最后階段(初始化后)創(chuàng)建的。但如果出現(xiàn)循環(huán)依賴,就需要提前創(chuàng)建代理對象。三級緩存中的工廠對象可以在需要時(shí)(即真正出現(xiàn)循環(huán)依賴時(shí))才創(chuàng)建代理對象,而不是對所有Bean都提前創(chuàng)建代理。
這種設(shè)計(jì)有以下優(yōu)勢:
- 延遲代理對象的創(chuàng)建:只有在真正需要時(shí)才創(chuàng)建代理對象,避免了不必要的性能開銷
- 保持Bean生命周期的一致性:盡可能地保持Bean的標(biāo)準(zhǔn)生命周期流程
- 靈活處理各種代理場景:適應(yīng)不同的AOP實(shí)現(xiàn)和代理方式
6. 三級緩存的局限性
雖然三級緩存機(jī)制能夠解決大多數(shù)循環(huán)依賴問題,但它仍有一些局限性:
- 不能解決構(gòu)造器注入的循環(huán)依賴:因?yàn)闃?gòu)造器注入發(fā)生在實(shí)例化階段,此時(shí)Bean還未被放入三級緩存,所以無法解決
- 不能解決prototype作用域的循環(huán)依賴:三級緩存機(jī)制只對單例Bean有效,對于prototype作用域的Bean,Spring不會緩存其實(shí)例,因此無法解決其循環(huán)依賴
- 不能解決多例Bean之間的循環(huán)依賴:原因同上,Spring不會緩存非單例Bean
7. 源碼分析
Spring解決循環(huán)依賴的核心源碼主要在DefaultSingletonBeanRegistry類中,關(guān)鍵方法包括:
getSingleton(String beanName):按照一級、二級、三級緩存的順序查找BeanaddSingletonFactory(String beanName, ObjectFactory<?> singletonFactory):將Bean的工廠對象放入三級緩存doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly):獲取Bean的主要邏輯
在AbstractAutowireCapableBeanFactory的doCreateBean方法中,會在Bean實(shí)例化后立即將其工廠對象放入三級緩存,為解決循環(huán)依賴做準(zhǔn)備。
8. 總結(jié)
Spring通過三級緩存機(jī)制巧妙地解決了循環(huán)依賴問題,其核心思想是將Bean的實(shí)例化和初始化分離,提前暴露實(shí)例化但未完全初始化的對象。三級緩存的設(shè)計(jì)不僅解決了基本的循環(huán)依賴問題,還優(yōu)雅地處理了AOP代理場景下的循環(huán)依賴。
這種機(jī)制體現(xiàn)了Spring框架設(shè)計(jì)的精妙之處,通過緩存分層和提前暴露對象的方式,在不影響B(tài)ean正常生命周期的前提下解決了看似棘手的循環(huán)依賴問題。
二、實(shí)際示例與解析
為了更直觀地理解Spring如何通過三級緩存解決循環(huán)依賴問題,我們來看一個(gè)具體的示例,并詳細(xì)分析整個(gè)過程。
1. 示例代碼
首先,我們創(chuàng)建兩個(gè)相互依賴的Service類:
@Service
public class UserService {
@Autowired
private OrderService orderService;
public void findUserOrders(Long userId) {
System.out.println("查詢用戶訂單");
orderService.getOrdersByUserId(userId);
}
}
@Service
public class OrderService {
@Autowired
private UserService userService;
public List<Order> getOrdersByUserId(Long userId) {
System.out.println("獲取用戶訂單");
return new ArrayList<>();
}
public void notifyUser(Long orderId) {
System.out.println("通知用戶訂單狀態(tài)");
userService.findUserOrders(1L); // 調(diào)用UserService的方法
}
}在這個(gè)示例中,UserService依賴OrderService,而OrderService又依賴UserService,形成了典型的循環(huán)依賴。
2. 詳細(xì)解析Spring處理流程
讓我們詳細(xì)分析Spring如何處理這個(gè)循環(huán)依賴:
2.1 創(chuàng)建UserService
當(dāng)Spring容器啟動時(shí),會按照Bean定義順序開始創(chuàng)建Bean。假設(shè)先創(chuàng)建UserService:
1. 實(shí)例化UserService對象(僅調(diào)用構(gòu)造函數(shù),此時(shí)內(nèi)部屬性尚未賦值)
2. 將UserService實(shí)例的創(chuàng)建工廠添加到三級緩存(singletonFactories)中
singletonFactories.put("userService", () -> getEarlyBeanReference(beanName, mbd, userService實(shí)例))
3. 開始填充UserService的屬性,發(fā)現(xiàn)需要注入OrderService
2.2 創(chuàng)建OrderService
由于UserService依賴OrderService,Spring開始創(chuàng)建OrderService:
1. 實(shí)例化OrderService對象(僅調(diào)用構(gòu)造函數(shù))
2. 將OrderService實(shí)例的創(chuàng)建工廠添加到三級緩存中
singletonFactories.put("orderService", () -> getEarlyBeanReference(beanName, mbd, orderService實(shí)例))
3. 開始填充OrderService的屬性,發(fā)現(xiàn)需要注入U(xiǎn)serService
2.3 處理循環(huán)依賴
此時(shí)出現(xiàn)了關(guān)鍵的循環(huán)依賴處理步驟:
1. OrderService需要注入U(xiǎn)serService,但UserService正在創(chuàng)建中
2. Spring嘗試從一級緩存(singletonObjects)查找UserService,未找到
3. 繼續(xù)從二級緩存(earlySingletonObjects)查找UserService,未找到
4. 最后從三級緩存(singletonFactories)中找到UserService的工廠對象
5. 調(diào)用工廠對象的getObject()方法獲取UserService的早期引用
- 如果UserService需要被代理(如有@Transactional注解),此時(shí)會創(chuàng)建代理對象
- 如果不需要代理,則返回原始對象
6. 將獲取到的UserService早期引用放入二級緩存,同時(shí)從三級緩存中移除
earlySingletonObjects.put("userService", userService早期引用)
singletonFactories.remove("userService")
7. 將UserService的早期引用注入到OrderService中
2.4 完成Bean創(chuàng)建
接下來完成兩個(gè)Bean的創(chuàng)建過程:
1. OrderService完成屬性填充(已注入U(xiǎn)serService的早期引用)
2. OrderService完成初始化(調(diào)用各種初始化方法)
3. 如果OrderService需要被代理,創(chuàng)建代理對象(AOP)
4. 將完全初始化好的OrderService放入一級緩存
singletonObjects.put("orderService", 完全初始化的orderService)
5. 返回到UserService的屬性填充流程,將完全初始化好的OrderService注入到UserService中
6. UserService完成初始化
7. 如果UserService需要被代理且之前沒有提前創(chuàng)建代理,創(chuàng)建代理對象
8. 將完全初始化好的UserService放入一級緩存
singletonObjects.put("userService", 完全初始化的userService)
3. 關(guān)鍵源碼執(zhí)行分析
讓我們看一下在這個(gè)過程中涉及的關(guān)鍵源碼執(zhí)行流程:
3.1 從getSingleton方法開始
當(dāng)Spring嘗試獲取一個(gè)Bean時(shí),首先會調(diào)用getSingleton方法:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 首先從一級緩存查找
Object singletonObject = this.singletonObjects.get(beanName);
// 如果一級緩存沒有,且該Bean正在創(chuàng)建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 從二級緩存查找
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;
}3.2 添加Bean到三級緩存
在Bean實(shí)例化后,Spring會將其添加到三級緩存中:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
// 如果一級緩存中不存在
if (!this.singletonObjects.containsKey(beanName)) {
// 添加到三級緩存
this.singletonFactories.put(beanName, singletonFactory);
// 確保二級緩存中不存在
this.earlySingletonObjects.remove(beanName);
// 記錄注冊的單例
this.registeredSingletons.add(beanName);
}
}
}3.3 創(chuàng)建早期引用
當(dāng)需要處理循環(huán)依賴時(shí),Spring會通過getEarlyBeanReference方法獲取早期引用:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp =
(SmartInstantiationAwareBeanPostProcessor) bp;
// 這里可能創(chuàng)建代理對象
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
4. AOP場景下的循環(huán)依賴
特別值得注意的是,當(dāng)涉及到AOP代理時(shí),循環(huán)依賴的處理會更加復(fù)雜。例如,如果我們的示例中添加了事務(wù)注解:
@Service
@Transactional
public class UserService {
// ...
}
@Service
@Transactional
public class OrderService {
// ...
}在這種情況下:
- 當(dāng)從三級緩存獲取UserService的早期引用時(shí),會通過
AbstractAutoProxyCreator.getEarlyBeanReference方法創(chuàng)建代理對象 - 這個(gè)代理對象會被放入二級緩存,并最終注入到OrderService中
- 這就確保了OrderService依賴的是UserService的代理對象,而不是原始對象
這也解釋了為什么需要三級緩存而不是二級緩存:三級緩存中存儲的工廠可以在需要時(shí)(出現(xiàn)循環(huán)依賴時(shí))才創(chuàng)建代理對象,而不是對所有Bean都提前創(chuàng)建代理。
5. 總結(jié)
通過這個(gè)實(shí)際示例,我們可以清晰地看到Spring如何通過三級緩存機(jī)制解決循環(huán)依賴問題:
- 實(shí)例化Bean后立即將其工廠對象放入三級緩存
- 當(dāng)出現(xiàn)循環(huán)依賴時(shí),通過三級緩存獲取早期引用(可能是代理對象)
- 將早期引用放入二級緩存,并從三級緩存中移除
- 使用早期引用完成依賴注入
- 最終將完全初始化的Bean放入一級緩存
這種機(jī)制既解決了循環(huán)依賴問題,又保持了Spring Bean生命周期的完整性,同時(shí)還能靈活處理AOP代理場景,體現(xiàn)了Spring框架設(shè)計(jì)的精妙之處。
到此這篇關(guān)于Spring中循環(huán)依賴問題的解決機(jī)制總結(jié)的文章就介紹到這了,更多相關(guān)Spring循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java版本怎么選以及JDK各版本特性對比與實(shí)戰(zhàn)建議
Java是一種廣泛使用的編程語言,擁有一個(gè)龐大的社區(qū)和大量的生態(tài)系統(tǒng),自從Java的早期版本以來,它已經(jīng)經(jīng)歷了許多變化和改進(jìn),這篇文章主要介紹了Java版本怎么選以及JDK各版本特性對比與實(shí)戰(zhàn)建議的相關(guān)資料,需要的朋友可以參考下2025-11-11
Java8優(yōu)雅的字符串拼接工具類StringJoiner實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于Java8優(yōu)雅的字符串拼接工具類StringJoiner的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
Lombok中@EqualsAndHashCode注解的使用及說明
這篇文章主要介紹了Lombok中@EqualsAndHashCode注解的使用及說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
java實(shí)現(xiàn)的根據(jù)概率隨機(jī)中獎測試類
這篇文章主要介紹了java實(shí)現(xiàn)的根據(jù)概率隨機(jī)中獎測試類,結(jié)合完整實(shí)例形式詳細(xì)分析了java隨機(jī)數(shù)實(shí)現(xiàn)概率運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下2019-09-09
springboot+dubbo實(shí)現(xiàn)時(shí)間輪算法
時(shí)間輪是一種高效利用線程資源進(jìn)行批量化調(diào)度的算法,本文主要介紹了springboot+dubbo實(shí)現(xiàn)時(shí)間輪算法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04
SpringBoot整合Mybatis與druid實(shí)現(xiàn)流程詳解
這篇文章主要介紹了springboot整合mybatis plus與druid詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的下伙伴可以參考一下2022-10-10
SpringBoot整合RabbitMQ處理死信隊(duì)列和延遲隊(duì)列
這篇文章將通過示例為大家詳細(xì)介紹SpringBoot整合RabbitMQ時(shí)如何處理死信隊(duì)列和延遲隊(duì)列,文中的示例代碼講解詳細(xì),需要的可以參考一下2022-05-05

