Springboot詳細(xì)講解循環(huán)依賴(lài)
一、循環(huán)依賴(lài)
顧名思義多個(gè)類(lèi)中的依賴(lài)形成了環(huán)路,形成了類(lèi)似于死鎖的情況,導(dǎo)致springboot在啟動(dòng)時(shí)無(wú)法為我們創(chuàng)建Bean。通俗來(lái)說(shuō) 就是beanA中依賴(lài)了beanB,beanB中也依賴(lài)了beanA。
spring是支持循環(huán)依賴(lài)的,但是默認(rèn)只支持單例的循環(huán)依賴(lài),如果bean中依賴(lài)了原型bean,則需要加上lookup方法。Spring會(huì)為我們解決循環(huán)依賴(lài)。
@Autowired是通過(guò)三級(jí)緩存來(lái)解決循環(huán)依賴(lài)的。
@Autowired進(jìn)行屬性注入可以解決循環(huán)依賴(lài)。原理是: Spring控制了bean的生命周期,先實(shí)例化bean,后注入bean的屬性。 Spring中記錄了正在創(chuàng)建中的bean(已經(jīng)實(shí)例化但還沒(méi)初始化完畢的bean),所以可以再注入屬性是,從記錄的ben中去依賴(lài)的對(duì)象。
相對(duì)而言,單純使用構(gòu)造器注入就無(wú)法解決循環(huán)依賴(lài)。因?yàn)樵跇?gòu)造時(shí)就需要傳入依賴(lài)的對(duì)象,導(dǎo)致無(wú)法實(shí)例化。但是 構(gòu)造器注入可以使用@Lazy解決循環(huán)依賴(lài),在實(shí)例化時(shí),傳入代理對(duì)象,真正使用時(shí)才會(huì)生成真正的對(duì)象。
二、循環(huán)依賴(lài)形成條件(使用構(gòu)造器注入)
1、使用構(gòu)造方法的方式來(lái)注入依賴(lài),并且類(lèi)A中依賴(lài)類(lèi)B,類(lèi)B也同時(shí)依賴(lài)類(lèi)A,這樣兩個(gè)類(lèi)都無(wú)法正常進(jìn)行Bean的創(chuàng)建,就會(huì)拋出異常:BeanCurrentlyInCreationException
@Component
public class A {
private B b;
@Autowired
public A(B b) {
this.b = b;
}
}@Component
public class B {
private A a;
@Autowired
public B(A a) {
this.a = a;
}
}解決方法之一:可以使用lazy注解,延遲加載依賴(lài)
@Component
public class A {
private B b;
@Autowired
@Lazy
public A(B b) {
this.b = b;
}
}@Component
public class B {
private A a;
@Autowired
@Lazy
public B(A a) {
this.a = a;
}
}三、循環(huán)依賴(lài)形成條件(@Aysnc注解的bean生成了對(duì)象的代理)

從日志中可以看到是 tPartnerOrgService 這個(gè)bean出現(xiàn)了循環(huán)依賴(lài)
我在tPartnerOrgService 中使用了@Aysnc注解 進(jìn)行異步處理,而@Aysnc注解的bean生成了對(duì)象的代理,導(dǎo)致Spring bean最終加載的不是一個(gè)原始對(duì)象導(dǎo)致了此次問(wèn)題的發(fā)生。
解決方案1:給 tPartnerOrgService 加上@Lazy注解

解決方案2:代碼優(yōu)化,不要讓@Async的Bean參與循環(huán)依賴(lài)
四、針對(duì)以上問(wèn)題對(duì)Spring如何解決循環(huán)依賴(lài)進(jìn)行詳細(xì)闡述

首先Spring維護(hù)了三個(gè)Map,也就是我們通常說(shuō)的三級(jí)緩存
singletonObjects:俗稱(chēng)單例池,緩存創(chuàng)建完成的單例BeansingletonFactories:映射創(chuàng)建Bean的原始工廠earlySingletonObjects:映射Bean的早期引用,也就是說(shuō)這個(gè)Map里的Bean不是完整的,只是完成了實(shí)例化,但還沒(méi)有初始化
Spring通過(guò)三級(jí)緩存解決了循環(huán)依賴(lài),其中一級(jí)緩存為單例池(singletonObjects),二級(jí)緩存為早期曝光對(duì)象earlySingletonObjects,三級(jí)緩存為早期曝光對(duì)象工廠(singletonFactories)。
當(dāng)A、B兩個(gè)類(lèi)發(fā)生循環(huán)引用時(shí),在A完成實(shí)例化后,就使用實(shí)例化后的對(duì)象去創(chuàng)建一個(gè)對(duì)象工廠,并添加到三級(jí)緩存中,如果A被AOP代理,那么通過(guò)這個(gè)工廠獲取到的就是A代理后的對(duì)象,如果A沒(méi)有被AOP代理,那么這個(gè)工廠獲取到的就是A實(shí)例化的對(duì)象。
當(dāng)A進(jìn)行屬性注入時(shí),會(huì)去創(chuàng)建B,同時(shí)B又依賴(lài)了A,所以創(chuàng)建B的同時(shí)又會(huì)去調(diào)用getBean(a)來(lái)獲取需要的依賴(lài),此時(shí)的getBean(a)會(huì)從緩存中獲取,第一步,先獲取到三級(jí)緩存中的工廠;第二步,調(diào)用對(duì)象工工廠的getObject方法來(lái)獲取到對(duì)應(yīng)的對(duì)象,得到這個(gè)對(duì)象后將其注入到B中。
緊接著B(niǎo)會(huì)走完它的生命周期流程,包括初始化、后置處理器等。當(dāng)B創(chuàng)建完后,會(huì)將B再注入到A中,此時(shí)A再完成它的整個(gè)生命周期。至此,循環(huán)依賴(lài)結(jié)束!
簡(jiǎn)單一句話說(shuō):先去緩存里找Bean,沒(méi)有則實(shí)例化當(dāng)前的Bean放到Map,如果有需要依賴(lài)當(dāng)前Bean的,就能從Map取到。
針對(duì)上面的@Aysnc注解產(chǎn)生的循環(huán)依賴(lài)進(jìn)行分析:
有@Aysnc注解的bean最后生成了一個(gè)代理對(duì)象,我們結(jié)合Spring bean創(chuàng)建的流程來(lái)分析這次問(wèn)題。
beanA開(kāi)始初始化,beanA實(shí)例化完成后給beanA的依賴(lài)屬性beanB進(jìn)行賦值beanB開(kāi)始初始化,beanB實(shí)例化完成后給beanB的依賴(lài)屬性beanA進(jìn)行賦值- 因?yàn)?code>beanA是支持循環(huán)依賴(lài)的,所以可以在
earlySingletonObjects中可以拿到beanA的早期引用的,但是因?yàn)?code>beanB打了@Aysnc注解并不能在earlySingletonObjects中可以拿到早期引用 - 接下來(lái)執(zhí)行執(zhí)行
initializeBean(Object existingBean, String beanName)方法,這里beanA可以正常實(shí)例化完成,但是因?yàn)?code>beanB打了@Aysnc注解,所以向Spring IOC容器中增加了一個(gè)代理對(duì)象,也就是說(shuō)beanA的beanB并不是一個(gè)原始對(duì)象,而是一個(gè)代理對(duì)象 - 接下來(lái)進(jìn)行執(zhí)行
doCreateBean方法時(shí)對(duì)進(jìn)行檢測(cè),以下代碼有所刪減,只保留核心邏輯代碼
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
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);
// 重點(diǎn)在這里,這里會(huì)遍歷所有依賴(lài)的bean,如果beanA依賴(lài)beanB和緩存中的beanB不相等
// 也就是說(shuō)beanA本來(lái)依賴(lài)的是一個(gè)原始對(duì)象beanB,但是這個(gè)時(shí)候發(fā)現(xiàn)beanB是一個(gè)代理對(duì)象,就會(huì)增加到actualDependentBeans
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
// 發(fā)現(xiàn)actualDependentBeans不為空,就發(fā)生了我們最開(kāi)始截圖的錯(cuò)誤
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 " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}到此這篇關(guān)于Springboot詳細(xì)講解循環(huán)依賴(lài)的文章就介紹到這了,更多相關(guān)Springboot循環(huán)依賴(lài)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot循環(huán)依賴(lài)問(wèn)題案例代碼及解決辦法
- 如何解決SpringBoot2.6及之后版本取消了循環(huán)依賴(lài)的支持問(wèn)題
- SpringBoot3.x循環(huán)依賴(lài)問(wèn)題解決方案
- springboot配置允許循環(huán)依賴(lài)問(wèn)題
- SpringBoot啟動(dòng)報(bào)錯(cuò)屬性循環(huán)依賴(lài)報(bào)錯(cuò)問(wèn)題的解決
- SpringBoot2.6.x默認(rèn)禁用循環(huán)依賴(lài)后的問(wèn)題解決
- springboot bean循環(huán)依賴(lài)實(shí)現(xiàn)以及源碼分析
- 基于SpringBoot構(gòu)造器注入循環(huán)依賴(lài)及解決方式
- Springboot循環(huán)依賴(lài)的原因及解決
相關(guān)文章
Intellij IDEA 旗艦版創(chuàng)建 Spring MVC 項(xiàng)目踩過(guò)的坑
IDEA旗艦版可以直接創(chuàng)建Spring MVC項(xiàng)目,但創(chuàng)建后的項(xiàng)目并不是直接就可以運(yùn)行,還需要進(jìn)行一些配置。這篇文章主要介紹了Intellij IDEA 旗艦版創(chuàng)建 Spring MVC 項(xiàng)目踩坑記 ,需要的朋友可以參考下2020-03-03
在springboot中使用AOP進(jìn)行全局日志記錄
這篇文章主要介紹就在springboot中使用AOP進(jìn)行全局日志記錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
JAVA中關(guān)于Long類(lèi)型返回前端精度丟失問(wèn)題處理辦法
這篇文章主要介紹了后端JavaBean的id屬性從Long類(lèi)型改為雪花算法后出現(xiàn)的精度丟失問(wèn)題,解決方案包括將id字段類(lèi)型改為字符串或使用Jackson序列化方式,需要的朋友可以參考下2024-11-11
Spring?Boot開(kāi)發(fā)時(shí)Java對(duì)象和Json對(duì)象之間的轉(zhuǎn)換
在Spring?Boot開(kāi)發(fā)中,我們經(jīng)常需要處理Java對(duì)象和Json對(duì)象之間的轉(zhuǎn)換,本文將介紹如何在Spring?Boot項(xiàng)目中實(shí)現(xiàn)Java對(duì)象和Json對(duì)象之間的轉(zhuǎn)換,感興趣的朋友跟隨小編一起看看吧2023-09-09
Java 8 對(duì) HashSet 元素進(jìn)行排序的操作方法
Java 中HashSet是一個(gè)不保證元素順序的集合類(lèi),其內(nèi)部是基于 HashMap 實(shí)現(xiàn)的,HashSet不支持排序,我們?cè)谛枰獙?duì)HashSet 排序時(shí),必須將其轉(zhuǎn)換為支持排序的集合或數(shù)據(jù)結(jié)構(gòu),如 List,本文將詳細(xì)介紹在 Java 8 中如何對(duì) HashSet 中的元素進(jìn)行排序,感興趣的朋友一起看看吧2024-11-11
Java實(shí)現(xiàn)PDF轉(zhuǎn)為Word文檔的示例代碼
眾所周知,PDF文檔除了具有較強(qiáng)穩(wěn)定性和兼容性外,?還具有較強(qiáng)的安全性,在工作中可以有效避免別人無(wú)意中對(duì)文檔內(nèi)容進(jìn)行修改。本文將分為以下兩部分介紹如何在保持布局的情況下將PDF轉(zhuǎn)為Word文檔,希望對(duì)大家有所幫助2023-01-01

