一文詳解Spring是怎樣處理循環(huán)依賴的
環(huán)境
Spring Framework Version: 5.3.x
Gradle Version:7.5.1
什么是循環(huán)依賴?
簡單理解就是A,B 兩個bean相互依賴,A依賴B,B依賴A
A->B、B->A大概就是這樣.
所有注入場景的循環(huán)依賴Spring都能解決嗎?
答案是 “不”, Spring不能夠解決循環(huán)依賴的構(gòu)造器注入,其它的注入方式都能解決
**注意:**能解決非構(gòu)造器注入的循環(huán)依賴的前提是開啟允許循環(huán)依賴(allowCircularReferences = true),在spring中默認開啟,如果是在springboot2.x中,那么是默認關(guān)閉的
場景
我們來編寫一段代碼,模擬一下循環(huán)依賴場景
TestA.java
public class TestA {
private TestB testB;
public void setB(TestB testB) {
this.testB = testB;
}
public void testCircularReference() {
System.out.println("TestA bean register success...");
}
}
TestB.java
public class TestB {
private TestA testA;
public void setA(TestA testA) {
this.testA = testA;
}
public void testCircularReference() {
System.out.println("TestB bean register success...");
}
}
spring-context.xml
//使用的注入方式是自動裝配,根據(jù)type自動裝配 <bean id="testA" class="com.spring.demo.circularreference.TestA" autowire="byType"></bean> <bean id="testB" class="com.spring.demo.circularreference.TestB" autowire="byType"></bean>
Main.java
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
TestA testA = context.getBean(TestA.class);
TestB testB = context.getBean(TestB.class);
testA.testCircularReference();
testB.testCircularReference();
}
}
測試結(jié)果
a bean register success... b bean register success...
好,代碼編寫完畢,測試也沒問題,接下來我們來進行源碼解析
Spring是怎么解決循環(huán)依賴的?
前置說明
Spring是通過三級緩存來解決循環(huán)依賴的,我們來看一張圖片,認識一下三級緩存

一級緩存(singletonObjects):存放初始化完成的bean
二級緩存(earlySingletonObjects):存放實例化但未初始化的bean
三級緩存(singletonFactories):存放對象工廠,也就是把實例化但未初始化的bean包裝為ObjectFactory
認識了三級緩存后,我們來剖析一下Spring是怎么利用三級緩存來解決循環(huán)依賴的
在實例化TestA之后,我們先會把TestA添加到三級緩存,然后進行屬性填充,給TestB進行依賴注入,那么到TestB屬性填充時,發(fā)現(xiàn)TestB也依賴TestA,這個時候去注入TestA,先從一級緩存獲取,獲取不到,再從二級緩存獲取,二級緩存也沒有,這個時候從三級緩存獲取,獲取到了,然后getObject()生成TestA對象(在這個時候,如果有代理,那么生成代理對象,如果沒有代理,直接返回原本的對象),獲取到TestA對象后移到二級緩存并返回,這個時候TestB就成功注入了TestA,然后TestB順利的走完生命周期,就回到了第一個TestA,進行初始化,初始化完之后,再從二級緩存中取出再重新賦值(為了保證bean是同一個),最后添加到一級緩存中
這么說可能有點繞,我們結(jié)合一張圖來看看

大概就是這樣
- 添加半成品的TestA(實例化但沒進行屬性注入與初始化)到三級緩存,然后注入TestB,在TestB中也要進行屬性注入,然后就去注入TestA
- 在TestB中注入TestA時,直接從緩存獲取,因為這時三級緩存已經(jīng)存在TestA,然后調(diào)用getEarlyBeanReference方法生成對象(如果有代理也是在此生成)
- 添加到二級緩存中并刪除三級緩存,最后返回,TestB屬性注入完成,繼續(xù)走Bean的生命周期
- 回到原本TestA,這時TestB注入已經(jīng)完成,然后初始化,最后從二級緩存中獲取最新的bean,避免不是同一個對象(代理對象)
源碼解析
addSingletonFactory
? TestA在實例化之后添加三級緩存

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);
}
}
}
getSingleton
TestB屬性注入TestA過程中,從緩存獲取TestA
在這里會獲取到早期對象,移除三級緩存中的ObjectFactory
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 從一級緩存中獲取TestA,這個時候是沒有的
Object singletonObject = this.singletonObjects.get(beanName);
//isSingletonCurrentlyInCreation 在實例化之前就添加了創(chuàng)建標識,所以這里為true
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//從二級緩存中獲取TestA,這個時候二級緩存也是沒有的
singletonObject = this.earlySingletonObjects.get(beanName);
//allowEarlyReference 是否允許循環(huán)依賴
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
//雙重檢查
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
//從三級緩存中獲取ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//獲取早期暴露對象(代理對象也是在此生成)
singletonObject = singletonFactory.getObject();
//添加到二級緩存中
this.earlySingletonObjects.put(beanName, singletonObject);
//根據(jù)beanName從三級緩存中移除ObjectFactory
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
getEarlyBeanReference
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
//遍歷smartInstantiationAware類型的后處理器
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
//獲取早期bean引用
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
此時獲取到的TestA是個代理對象,那么TestB里注入的是代理對象,然后TestA成功注入TestB,但是原本的TestA還是個普通的對象,怎么辦呢?
很簡單,之前不是將對象放到二級緩存了嗎,所以在TestA注入完TestB并且初始化之后,這個時候會去二級緩存中獲取最新的bean,并重新賦值,保證是同一個對象,看代碼
//二級緩存提前暴露
if (earlySingletonExposure) {
//從二級緩存中獲取到最新的bean
Object earlySingletonReference = getSingleton(beanName, false);
//如果能獲取到并且exposedObject和實例化之后的bean是保持一致的,那么就進行重新賦值
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
至此,循環(huán)依賴就解析完畢了,這里有個需要注意的點,就是生成代理對象那一塊,需要依賴于SmartInstantiationAwareBeanPostProcessor生成代理的,是不能被處理的,例如@Async注解,它需要依賴于SmartInstantiationAwareBeanPostProcessor,被它代理的類,生成代理時機是初始化bean之后,那么如果在循環(huán)依賴里出現(xiàn),例如TestA、TestB互相依賴,那么TestA使用了@Async注解,那么它的代理生成時機在bean的初始化之后,這樣就會出現(xiàn)問題了,在TestB注入TestA時,從緩存中獲取TestA,這時是沒有被代理的,當原始的TestA注入完成后,在初始化之后生成代理,這個時候就會造成TestB里注入的TestA不是代理對象,而原始的TestA已經(jīng)變成代理對象了,就會造成不是同一個對象
總結(jié)
看完源碼之后,相信大家都有了一些了解,如果看完還是不太明白也沒關(guān)系,自己跟著debug一遍然后做總結(jié),加深印象,接下來我們對以上做一下總結(jié)吧。
如果被問到Spring是如何解決循環(huán)依賴的?
答: Spring是通過三級緩存去解決的循環(huán)依賴,具體來說就是在TestA實例化之后,屬性填充之前,把Test包裝成ObjectFactory對象并存入三級緩存中,這時注入TestB,然后在TestB里注入TestA時,就會從三級緩存里getObject,取出TestA半成品對象(如果是代理對象就進行創(chuàng)建),并且配合二級緩存,把它存入二級緩存中并在三級緩存中刪除,最后回到原始TestA,在初始化原TestA之后,進行重新賦值,避免不是同一個對象。
為什么構(gòu)造器注入不能解決循環(huán)依賴?
**答:**因為構(gòu)造器注入是在實例化bean的時候,這時候三級緩存還沒有添加,所以不能解決循環(huán)依賴。
為什么要設(shè)計三級緩存,一級、二級緩存行不行?
這個問題非常值得思考,不過不要陷入其中,大家可以思考一下
以上就是一文詳解Spring是怎樣處理循環(huán)依賴的的詳細內(nèi)容,更多關(guān)于Spring處理循環(huán)依賴的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java使用modbus-master-tcp實現(xiàn)modbus tcp通訊
這篇文章主要為大家詳細介紹了另外一種Java語言的modbux tcp通訊方案,那就是modbus-master-tcp,文中的示例代碼講解詳細,需要的可以了解下2023-12-12
在IDEA中配置Selenium和WebDriver的具體操作
在自動化測試領(lǐng)域Selenium是一款非常流行的開源工具,它支持多種瀏覽器,并提供了豐富的API供開發(fā)者使用,而WebDriver則是Selenium的一個重要組件,它負責驅(qū)動瀏覽器執(zhí)行測試腳本,這篇文章主要給大家介紹了在IDEA中配置Selenium和WebDriver的具體操作,需要的朋友可以參考下2024-10-10
使用IntelliJ IDEA 進行代碼對比的方法(兩種方法)
這篇文章給大家?guī)砹藘煞NIntelliJ IDEA 進行代碼對比的方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2018-01-01
基于java中stack與heap的區(qū)別,java中的垃圾回收機制的相關(guān)介紹
本篇文章小編將為大家介紹,基于java中stack與heap的區(qū)別,java中的垃圾回收機制的相關(guān)介紹,需要的可以參考一下2013-04-04
通過spring注解開發(fā),簡單測試單例和多例區(qū)別
這篇文章主要介紹了通過spring注解開發(fā),簡單測試單例和多例區(qū)別,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08

