Bean的循環(huán)依賴問題全面解析
什么是Bean的循環(huán)依賴
A對象中有B屬性。B對象中有A屬性。這就是循環(huán)依賴。我依賴你,你也依賴我。
比如:丈夫類Husband,妻子類Wife。Husband中有Wife的引用。Wife中有Husband的引用。

package com.powernode.spring6.bean;
/**
* @author 動力節(jié)點(diǎn)
* @version 1.0
* @className Husband
* @since 1.0
**/
public class Husband {
private String name;
private Wife wife;
}package com.powernode.spring6.bean;
/**
* @author 動力節(jié)點(diǎn)
* @version 1.0
* @className Wife
* @since 1.0
**/
public class Wife {
private String name;
private Husband husband;
}singleton下的set注入產(chǎn)生的循環(huán)依賴
我們來編寫程序,測試一下在singleton+setter的模式下產(chǎn)生的循環(huán)依賴,Spring是否能夠解決?
package com.powernode.spring6.bean;
/**
* @author 動力節(jié)點(diǎn)
* @version 1.0
* @className Husband
* @since 1.0
**/
public class Husband {
private String name;
private Wife wife;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
// toString()方法重寫時需要注意:不能直接輸出wife,輸出wife.getName()。要不然會出現(xiàn)遞歸導(dǎo)致的棧內(nèi)存溢出錯誤。
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}package com.powernode.spring6.bean;
/**
* @author 動力節(jié)點(diǎn)
* @version 1.0
* @className Wife
* @since 1.0
**/
public class Wife {
private String name;
private Husband husband;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
// toString()方法重寫時需要注意:不能直接輸出husband,輸出husband.getName()。要不然會出現(xiàn)遞歸導(dǎo)致的棧內(nèi)存溢出錯誤。
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="singleton">
<property name="name" value="張三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singleton">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
</bean>
</beans>package com.powernode.spring6.test;
import com.powernode.spring6.bean.Husband;
import com.powernode.spring6.bean.Wife;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 動力節(jié)點(diǎn)
* @version 1.0
* @className CircularDependencyTest
* @since 1.0
**/
public class CircularDependencyTest {
@Test
public void testSingletonAndSet(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
System.out.println(husbandBean);
System.out.println(wifeBean);
}
}通過測試得知:在singleton + set注入的情況下,循環(huán)依賴是沒有問題的。Spring可以解決這個問題。
Spring解決循環(huán)依賴的機(jī)理
Spring為什么可以解決set + singleton模式下循環(huán)依賴?
根本的原因在于:這種方式可以做到將“實(shí)例化Bean”和“給Bean屬性賦值”這兩個動作分開去完成。
實(shí)例化Bean的時候:調(diào)用無參數(shù)構(gòu)造方法來完成。此時可以先不給屬性賦值,可以提前將該Bean對象“曝光”給外界。
給Bean屬性賦值的時候:調(diào)用setter方法來完成。
兩個步驟是完全可以分離開去完成的,并且這兩步不要求在同一個時間點(diǎn)上完成。
也就是說,Bean都是單例的,我們可以先把所有的單例Bean實(shí)例化出來,放到一個集合當(dāng)中(我們可以稱之為緩存),所有的單例Bean全部實(shí)例化完成之后,以后我們再慢慢的調(diào)用setter方法給屬性賦值。這樣就解決了循環(huán)依賴的問題。
那么在Spring框架底層源碼級別上是如何實(shí)現(xiàn)的呢?請看:

在以上類中包含三個重要的屬性:
**Cache of singleton objects: bean name to bean instance. **單例對象的緩存:key存儲bean名稱,value存儲Bean對象【一級緩存】
**Cache of early singleton objects: bean name to bean instance. **早期單例對象的緩存:key存儲bean名稱,value存儲早期的Bean對象【二級緩存】
**Cache of singleton factories: bean name to ObjectFactory. **單例工廠緩存:key存儲bean名稱,value存儲該Bean對應(yīng)的ObjectFactory對象【三級緩存】
這三個緩存其實(shí)本質(zhì)上是三個Map集合。
我們再來看,在該類中有這樣一個方法addSingletonFactory(),這個方法的作用是:將創(chuàng)建Bean對象的ObjectFactory對象提前曝光。

再分析下面的源碼:

從源碼中可以看到,spring會先從一級緩存中獲取Bean,如果獲取不到,則從二級緩存中獲取Bean,如果二級緩存還是獲取不到,則從三級緩存中獲取之前曝光的ObjectFactory對象,通過ObjectFactory對象獲取Bean實(shí)例,這樣就解決了循環(huán)依賴的問題。
總結(jié):
Spring只能解決setter方法注入的單例bean之間的循環(huán)依賴。ClassA依賴ClassB,ClassB又依賴ClassA,形成依賴閉環(huán)。Spring在創(chuàng)建ClassA對象后,不需要等給屬性賦值,直接將其曝光到bean緩存當(dāng)中。在解析ClassA的屬性時,又發(fā)現(xiàn)依賴于ClassB,再次去獲取ClassB,當(dāng)解析ClassB的屬性時,又發(fā)現(xiàn)需要ClassA的屬性,但此時的ClassA已經(jīng)被提前曝光加入了正在創(chuàng)建的bean的緩存中,則無需創(chuàng)建新的的ClassA的實(shí)例,直接從緩存中獲取即可。從而解決循環(huán)依賴問題。
到此這篇關(guān)于Bean的循環(huán)依賴問題的文章就介紹到這了,更多相關(guān)Bean循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot怎么解決循環(huán)依賴(基于單例 Bean)
- Spring Bean創(chuàng)建和循環(huán)依賴
- 關(guān)于Spring源碼是如何解決Bean的循環(huán)依賴
- Java Spring循環(huán)依賴原理與bean的生命周期圖文案例詳解
- springboot bean循環(huán)依賴實(shí)現(xiàn)以及源碼分析
- Spring IOC原理補(bǔ)充說明(循環(huán)依賴、Bean作用域等)
- 詳解Spring-bean的循環(huán)依賴以及解決方式
- 詳解Spring Bean的循環(huán)依賴解決方案
- Spring循環(huán)依賴正確性及Bean注入的順序關(guān)系詳解
相關(guān)文章
mybatis-plus自定義業(yè)務(wù)實(shí)現(xiàn)方式
本文介紹了使用MyBatis-Plus(簡稱MP)進(jìn)行數(shù)據(jù)庫操作的實(shí)現(xiàn)步驟,包括創(chuàng)建實(shí)體類、Mapper接口和XML文件、Service接口和實(shí)現(xiàn)類,以及測試類,重點(diǎn)強(qiáng)調(diào)了業(yè)務(wù)層參數(shù)校驗、事務(wù)管理以及復(fù)雜SQL的處理方式2026-03-03
MyBatis 參數(shù)綁定的具體實(shí)現(xiàn)
本文主要介紹了MyBatis 參數(shù)綁定的具體實(shí)現(xiàn),包括默認(rèn)參數(shù)名、@Param注解和POJO/DTO對象三種方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2026-01-01
解決Kibana報錯[security_exception] current license&nbs
ElasticSearch 5.1.1因安全許可證過期報錯,解決方法:官網(wǎng)申請新許可證,下載JSON文件并上傳至服務(wù)器,執(zhí)行代碼時切換root權(quán)限,配置host為服務(wù)器IP,端口9200,替換原license文件2025-07-07
SpringBoot多環(huán)境切換的靈活配置詳細(xì)教程
在真實(shí)項目開發(fā)的時候,一定會有多個環(huán)境,下面這篇文章主要給大家介紹了關(guān)于SpringBoot多環(huán)境切換靈活配置的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04
Spring MVC實(shí)現(xiàn)的登錄攔截器代碼分享
這篇文章主要介紹了Spring MVC實(shí)現(xiàn)的登錄攔截器代碼分享,涉及攔截器的簡單介紹,攔截器和過濾器的區(qū)以及攔截器實(shí)現(xiàn)代碼等相關(guān)內(nèi)容,這里分享給大家,供需要的朋友參考。2017-10-10
Springboot的application.properties或application.yml環(huán)境的指定運(yùn)行與配置方
本文主要介紹了Spring?Boot中配置文件的多種使用方式,包括配置文件的命名、激活、路徑指定以及優(yōu)先級,并結(jié)合示例進(jìn)行了詳細(xì)說明2025-11-11
詳解Java異常處理中throw與throws關(guān)鍵字的用法區(qū)別
這篇文章主要介紹了詳解Java異常處理中throw與throws關(guān)鍵字的用法區(qū)別,這也是Java面試題目中的常客,需要的朋友可以參考下2015-11-11
Java使用TCP實(shí)現(xiàn)在線聊天的示例代碼
這篇文章主要介紹了Java使用TCP實(shí)現(xiàn)在線聊天的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01

