一文詳解Spring是怎樣處理循環(huán)依賴(lài)的
環(huán)境
Spring Framework Version: 5.3.x
Gradle Version:7.5.1
什么是循環(huán)依賴(lài)?
簡(jiǎn)單理解就是A,B 兩個(gè)bean相互依賴(lài),A依賴(lài)B,B依賴(lài)A
A->B、B->A大概就是這樣.
所有注入場(chǎng)景的循環(huán)依賴(lài)Spring都能解決嗎?
答案是 “不”, Spring不能夠解決循環(huán)依賴(lài)的構(gòu)造器注入,其它的注入方式都能解決
**注意:**能解決非構(gòu)造器注入的循環(huán)依賴(lài)的前提是開(kāi)啟允許循環(huán)依賴(lài)(allowCircularReferences = true),在spring中默認(rèn)開(kāi)啟,如果是在springboot2.x中,那么是默認(rèn)關(guān)閉的
場(chǎng)景
我們來(lái)編寫(xiě)一段代碼,模擬一下循環(huán)依賴(lài)場(chǎng)景
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
//使用的注入方式是自動(dòng)裝配,根據(jù)type自動(dòng)裝配 <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(); } }
測(cè)試結(jié)果
a bean register success... b bean register success...
好,代碼編寫(xiě)完畢,測(cè)試也沒(méi)問(wèn)題,接下來(lái)我們來(lái)進(jìn)行源碼解析
Spring是怎么解決循環(huán)依賴(lài)的?
前置說(shuō)明
Spring是通過(guò)三級(jí)緩存來(lái)解決循環(huán)依賴(lài)的,我們來(lái)看一張圖片,認(rèn)識(shí)一下三級(jí)緩存
一級(jí)緩存(singletonObjects):存放初始化完成的bean
二級(jí)緩存(earlySingletonObjects):存放實(shí)例化但未初始化的bean
三級(jí)緩存(singletonFactories):存放對(duì)象工廠(chǎng),也就是把實(shí)例化但未初始化的bean包裝為ObjectFactory
認(rèn)識(shí)了三級(jí)緩存后,我們來(lái)剖析一下Spring是怎么利用三級(jí)緩存來(lái)解決循環(huán)依賴(lài)的
在實(shí)例化TestA之后,我們先會(huì)把TestA添加到三級(jí)緩存,然后進(jìn)行屬性填充,給TestB進(jìn)行依賴(lài)注入,那么到TestB屬性填充時(shí),發(fā)現(xiàn)TestB也依賴(lài)TestA,這個(gè)時(shí)候去注入TestA,先從一級(jí)緩存獲取,獲取不到,再?gòu)亩?jí)緩存獲取,二級(jí)緩存也沒(méi)有,這個(gè)時(shí)候從三級(jí)緩存獲取,獲取到了,然后getObject()生成TestA對(duì)象(在這個(gè)時(shí)候,如果有代理,那么生成代理對(duì)象,如果沒(méi)有代理,直接返回原本的對(duì)象),獲取到TestA對(duì)象后移到二級(jí)緩存并返回,這個(gè)時(shí)候TestB就成功注入了TestA,然后TestB順利的走完生命周期,就回到了第一個(gè)TestA,進(jìn)行初始化,初始化完之后,再?gòu)亩?jí)緩存中取出再重新賦值(為了保證bean是同一個(gè)),最后添加到一級(jí)緩存中
這么說(shuō)可能有點(diǎn)繞,我們結(jié)合一張圖來(lái)看看
大概就是這樣
- 添加半成品的TestA(實(shí)例化但沒(méi)進(jìn)行屬性注入與初始化)到三級(jí)緩存,然后注入TestB,在TestB中也要進(jìn)行屬性注入,然后就去注入TestA
- 在TestB中注入TestA時(shí),直接從緩存獲取,因?yàn)檫@時(shí)三級(jí)緩存已經(jīng)存在TestA,然后調(diào)用getEarlyBeanReference方法生成對(duì)象(如果有代理也是在此生成)
- 添加到二級(jí)緩存中并刪除三級(jí)緩存,最后返回,TestB屬性注入完成,繼續(xù)走Bean的生命周期
- 回到原本TestA,這時(shí)TestB注入已經(jīng)完成,然后初始化,最后從二級(jí)緩存中獲取最新的bean,避免不是同一個(gè)對(duì)象(代理對(duì)象)
源碼解析
addSingletonFactory
? TestA在實(shí)例化之后添加三級(jí)緩存
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { //添加三級(jí)緩存 this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
getSingleton
TestB屬性注入TestA過(guò)程中,從緩存獲取TestA
在這里會(huì)獲取到早期對(duì)象,移除三級(jí)緩存中的ObjectFactory
@Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 從一級(jí)緩存中獲取TestA,這個(gè)時(shí)候是沒(méi)有的 Object singletonObject = this.singletonObjects.get(beanName); //isSingletonCurrentlyInCreation 在實(shí)例化之前就添加了創(chuàng)建標(biāo)識(shí),所以這里為true if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { //從二級(jí)緩存中獲取TestA,這個(gè)時(shí)候二級(jí)緩存也是沒(méi)有的 singletonObject = this.earlySingletonObjects.get(beanName); //allowEarlyReference 是否允許循環(huán)依賴(lài) if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { //雙重檢查 singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { //從三級(jí)緩存中獲取ObjectFactory ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //獲取早期暴露對(duì)象(代理對(duì)象也是在此生成) singletonObject = singletonFactory.getObject(); //添加到二級(jí)緩存中 this.earlySingletonObjects.put(beanName, singletonObject); //根據(jù)beanName從三級(jí)緩存中移除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類(lèi)型的后處理器 for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { //獲取早期bean引用 exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }
此時(shí)獲取到的TestA是個(gè)代理對(duì)象,那么TestB里注入的是代理對(duì)象,然后TestA成功注入TestB,但是原本的TestA還是個(gè)普通的對(duì)象,怎么辦呢?
很簡(jiǎn)單,之前不是將對(duì)象放到二級(jí)緩存了嗎,所以在TestA注入完TestB并且初始化之后,這個(gè)時(shí)候會(huì)去二級(jí)緩存中獲取最新的bean,并重新賦值,保證是同一個(gè)對(duì)象,看代碼
//二級(jí)緩存提前暴露 if (earlySingletonExposure) { //從二級(jí)緩存中獲取到最新的bean Object earlySingletonReference = getSingleton(beanName, false); //如果能獲取到并且exposedObject和實(shí)例化之后的bean是保持一致的,那么就進(jìn)行重新賦值 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)依賴(lài)就解析完畢了,這里有個(gè)需要注意的點(diǎn),就是生成代理對(duì)象那一塊,需要依賴(lài)于SmartInstantiationAwareBeanPostProcessor生成代理的,是不能被處理的,例如@Async注解,它需要依賴(lài)于SmartInstantiationAwareBeanPostProcessor,被它代理的類(lèi),生成代理時(shí)機(jī)是初始化bean之后,那么如果在循環(huán)依賴(lài)?yán)锍霈F(xiàn),例如TestA、TestB互相依賴(lài),那么TestA使用了@Async注解,那么它的代理生成時(shí)機(jī)在bean的初始化之后,這樣就會(huì)出現(xiàn)問(wèn)題了,在TestB注入TestA時(shí),從緩存中獲取TestA,這時(shí)是沒(méi)有被代理的,當(dāng)原始的TestA注入完成后,在初始化之后生成代理,這個(gè)時(shí)候就會(huì)造成TestB里注入的TestA不是代理對(duì)象,而原始的TestA已經(jīng)變成代理對(duì)象了,就會(huì)造成不是同一個(gè)對(duì)象
總結(jié)
看完源碼之后,相信大家都有了一些了解,如果看完還是不太明白也沒(méi)關(guān)系,自己跟著debug一遍然后做總結(jié),加深印象,接下來(lái)我們對(duì)以上做一下總結(jié)吧。
如果被問(wèn)到Spring是如何解決循環(huán)依賴(lài)的?
答: Spring是通過(guò)三級(jí)緩存去解決的循環(huán)依賴(lài),具體來(lái)說(shuō)就是在TestA實(shí)例化之后,屬性填充之前,把Test包裝成ObjectFactory對(duì)象并存入三級(jí)緩存中,這時(shí)注入TestB,然后在TestB里注入TestA時(shí),就會(huì)從三級(jí)緩存里getObject,取出TestA半成品對(duì)象(如果是代理對(duì)象就進(jìn)行創(chuàng)建),并且配合二級(jí)緩存,把它存入二級(jí)緩存中并在三級(jí)緩存中刪除,最后回到原始TestA,在初始化原TestA之后,進(jìn)行重新賦值,避免不是同一個(gè)對(duì)象。
為什么構(gòu)造器注入不能解決循環(huán)依賴(lài)?
**答:**因?yàn)闃?gòu)造器注入是在實(shí)例化bean的時(shí)候,這時(shí)候三級(jí)緩存還沒(méi)有添加,所以不能解決循環(huán)依賴(lài)。
為什么要設(shè)計(jì)三級(jí)緩存,一級(jí)、二級(jí)緩存行不行?
這個(gè)問(wèn)題非常值得思考,不過(guò)不要陷入其中,大家可以思考一下
以上就是一文詳解Spring是怎樣處理循環(huán)依賴(lài)的的詳細(xì)內(nèi)容,更多關(guān)于Spring處理循環(huán)依賴(lài)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java使用modbus-master-tcp實(shí)現(xiàn)modbus tcp通訊
這篇文章主要為大家詳細(xì)介紹了另外一種Java語(yǔ)言的modbux tcp通訊方案,那就是modbus-master-tcp,文中的示例代碼講解詳細(xì),需要的可以了解下2023-12-12在IDEA中配置Selenium和WebDriver的具體操作
在自動(dòng)化測(cè)試領(lǐng)域Selenium是一款非常流行的開(kāi)源工具,它支持多種瀏覽器,并提供了豐富的API供開(kāi)發(fā)者使用,而WebDriver則是Selenium的一個(gè)重要組件,它負(fù)責(zé)驅(qū)動(dòng)瀏覽器執(zhí)行測(cè)試腳本,這篇文章主要給大家介紹了在IDEA中配置Selenium和WebDriver的具體操作,需要的朋友可以參考下2024-10-10使用IntelliJ IDEA 進(jìn)行代碼對(duì)比的方法(兩種方法)
這篇文章給大家?guī)?lái)了兩種IntelliJ IDEA 進(jìn)行代碼對(duì)比的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-01-01基于java中stack與heap的區(qū)別,java中的垃圾回收機(jī)制的相關(guān)介紹
本篇文章小編將為大家介紹,基于java中stack與heap的區(qū)別,java中的垃圾回收機(jī)制的相關(guān)介紹,需要的可以參考一下2013-04-04通過(guò)spring注解開(kāi)發(fā),簡(jiǎn)單測(cè)試單例和多例區(qū)別
這篇文章主要介紹了通過(guò)spring注解開(kāi)發(fā),簡(jiǎn)單測(cè)試單例和多例區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08從千千靜聽(tīng)歌詞服務(wù)器獲取lrc歌詞示例分享
這篇文章主要介紹了使用PHP從千千靜聽(tīng)歌詞服務(wù)器獲取lrc歌詞的方法,大家參考使用吧2014-01-01