Spring大白話(huà)之三級(jí)緩存如何解決循環(huán)依賴(lài)問(wèn)題
前言
在使用Spring 開(kāi)發(fā)過(guò)程中,我們需要對(duì)定義后的bean 通過(guò)構(gòu)造方法,或者bean 注入的方式注入到某個(gè)類(lèi)中進(jìn)而使用改bean 對(duì)應(yīng)的方法,在此過(guò)程中就會(huì)出現(xiàn)一個(gè)類(lèi)中注入了n多個(gè)bean,幾個(gè)類(lèi)中bean 相互注入,出現(xiàn) A 依賴(lài)B,B依賴(lài)C,C又依賴(lài)A/B 這種循環(huán)情況的出現(xiàn)。
提示:以下是本篇文章正文內(nèi)容,下面案例可供參考
一、Spring 循環(huán)依賴(lài)是什么?
循環(huán)依賴(lài)是指在Spring容器中,存在兩個(gè)或多個(gè)Bean之間的互相依賴(lài)關(guān)系,形成了一個(gè)閉環(huán)的依賴(lài)鏈。具體來(lái)說(shuō),當(dāng)Bean A依賴(lài)Bean B,同時(shí)Bean B又依賴(lài)Bean A時(shí),就產(chǎn)生了循環(huán)依賴(lài)。循環(huán)依賴(lài)可能會(huì)導(dǎo)致以下問(wèn)題:
死鎖:如果循環(huán)依賴(lài)的解析過(guò)程不正確,可能會(huì)導(dǎo)致死鎖。當(dāng)容器無(wú)法確定如何先實(shí)例化哪個(gè)Bean時(shí),可能會(huì)造成死鎖情況,導(dǎo)致應(yīng)用程序無(wú)法繼續(xù)正常執(zhí)行。

未完全初始化的Bean:在解決循環(huán)依賴(lài)時(shí),Spring使用了代理對(duì)象來(lái)解決依賴(lài)問(wèn)題。這意味著當(dāng)Bean A注入到Bean B中時(shí),A可能是一個(gè)未完全初始化的代理對(duì)象,而不是完全實(shí)例化,屬性注入,初始化的對(duì)象。這可能導(dǎo)致在早期階段的Bean存在一些限制和潛在的問(wèn)題。
為了解決循環(huán)依賴(lài)的問(wèn)題,Spring使用了一個(gè)兩階段的解析過(guò)程:實(shí)例化階段和注入階段。在實(shí)例化階段,Spring創(chuàng)建對(duì)象并將其放入緩存中;在注入階段,Spring解決依賴(lài)關(guān)系并完成注入。
SpringBoot 從 2.6 之前默認(rèn)開(kāi)啟循環(huán)依賴(lài),之后 開(kāi)始默認(rèn)不允許出現(xiàn) Bean 循環(huán)引用,如果需要?jiǎng)t進(jìn)行手動(dòng)開(kāi)啟:
spring:
main:
allow-circular-references:true二、Spring 三級(jí)緩存解決單例的循環(huán)依賴(lài)
Spring 三級(jí)緩存 實(shí)際上使用了3個(gè)Map 來(lái)打破單例對(duì)象循環(huán)依賴(lài)的問(wèn)題,singletonObjects:這是一級(jí)緩存,保存已經(jīng)實(shí)例化且完成了所有的依賴(lài)注入和初始化的單例bean實(shí)例。earlySingletonObjects:這是二級(jí)緩存,保存已經(jīng)實(shí)例化但尚未完成所有的依賴(lài)注入和初始化的單例bean實(shí)例。
它主要用于解決屬性注入時(shí)的循環(huán)依賴(lài)問(wèn)題。singletonFactories:這是三級(jí)緩存,保存創(chuàng)建單例bean實(shí)例的ObjectFactory。
2.1 Bean 單例對(duì)象生成的過(guò)程
對(duì)象的生成必須先通過(guò)其構(gòu)造方法進(jìn)行實(shí)例化,然后對(duì)其屬性賦值,然后執(zhí)行初始化;以下以 Aservice ,Bservice,Cservice 為例進(jìn)行研究;
public interface Aservice {
}
public interface Bservice {
}
public interface Cservice {
}
@Service
public class AserviceImpl implements Aservice {
@Autowired
private Bservice bservice;
}
@Service
public class BserviceImpl implements Bservice {
@Autowired
private Aservice aservice;
}
@Service
public class CserviceImpl implements Cservice {
@Autowired
private Aservice aservice;
@Autowired
private Bservice bservice;
}其中Aservice 依賴(lài)Bservice 的bean ,Bservice 依賴(lài)Aservice 的bean,Cservice 依賴(lài)Aservice ,Bservice 的bean ;

思考循環(huán)依賴(lài)的注入過(guò)程:
- 首先 A 實(shí)例化,通過(guò)A 類(lèi)的構(gòu)造方法進(jìn)行 實(shí)例的構(gòu)建并返回; 對(duì)其中的bservice 屬性值進(jìn)行依賴(lài)注入;
- 此時(shí)需要先從單例池中去獲取,Bservice 的bean ,Bservice 的bean 還沒(méi)有被創(chuàng)建,所以此時(shí)需要先創(chuàng)建Bservice 的bean ;
- 調(diào)用Bservice 的構(gòu)造方法進(jìn)行實(shí)例的創(chuàng)建,然后對(duì)Bservice 進(jìn)行屬性注入時(shí)需要 對(duì)Aservice 進(jìn)行 屬性值設(shè)置;
然后在從單例池中獲取Aservice 的bean,發(fā)現(xiàn)沒(méi)有Aservice 的bean ,這個(gè)時(shí)候如果重復(fù)在走Aservice 的bean 創(chuàng)建過(guò)程就會(huì)陷入死循環(huán),顯然我們的項(xiàng)目此時(shí)是可以成功啟動(dòng)的,也即沒(méi)有陷入死循環(huán)中,那么Spring 是怎么解決的?
如果說(shuō)在創(chuàng)建Aservice 的bean時(shí)是分為兩步:
- 步驟1: 先通過(guò)其構(gòu)造方法完成實(shí)例化;
- 步驟2: 對(duì)其屬性進(jìn)行填充;
那么如果我們?cè)诓襟E1 之后 ,就將還沒(méi)有完成初始化的Aservice 的bean 放入到某個(gè)地方,然后在初始化其他bean 的時(shí)候 如果發(fā)現(xiàn)依賴(lài)了Aservice 的bean 此時(shí)可以直接注入Aservice 的bean完成對(duì)其的引用,即使Aservice 的bean還沒(méi)有進(jìn)行完整的初始化,我們對(duì)Aservice 的bean 進(jìn)行了提前暴露;
這樣在Aservice 的bean真正完成初始化之后,對(duì)Aservice 的bean引用也隨即完成;這樣就打破了bean 的循環(huán)依賴(lài),bean 可以正常初始化了;
現(xiàn)在Bservice,Cservice 中都依賴(lài)了Aservice 的bean ,顯然無(wú)法對(duì)Aservice 的單例bean 實(shí)例化兩次 ,那么就需要有個(gè)地方來(lái)存放Aservice 的這個(gè)還沒(méi)有完全初始化的bean,這樣后續(xù)其它的bean 在注入Aservice 的bean 時(shí) 會(huì)發(fā)現(xiàn) Aservice 的bean 已經(jīng)有了,所以就可以直接使用,不需要在額外創(chuàng)建,這里spring 使用 map (二級(jí)緩存)來(lái)存放已經(jīng)實(shí)例化,但是還沒(méi)有完全初始化的 bean , 以便于在發(fā)生循環(huán)依賴(lài)時(shí),如果從單例池中獲取不到對(duì)應(yīng)的bean 就到二級(jí)緩存中在獲取一次,如果獲取到了可以直接使用,如果獲取不到則需要去生成這個(gè)bean 并將其放入到二級(jí)緩存中;

因?yàn)锽service 中已經(jīng)將Aservice的bean 放入到了二級(jí)緩存中,所以Cservice 可以直接從二級(jí)緩存中獲取到service 的單例bean ;
到此看起來(lái)spring 已經(jīng)通過(guò)二級(jí)緩存來(lái)提前暴露未初始化完成的bean 而解決了循環(huán)依賴(lài),那么為什么還有三級(jí)緩存的概念?
在原碼中可以看到三級(jí)緩存也是一個(gè)map ,并且其value 存的是一個(gè)對(duì)象的工廠
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
我們可能已經(jīng)聽(tīng)說(shuō)過(guò)三級(jí)緩存放入的是Lambda表達(dá)式 的匿名函數(shù),這個(gè)函數(shù)會(huì)在使用到的時(shí)候被調(diào)用,那么spring 為什么選擇放一個(gè)匿名函數(shù)而不是直接放入一個(gè)bean 呢;顯然如果直接放入一個(gè)bean 那么三級(jí)緩存的作用就和二級(jí)緩存相同了;所以spring 這樣做肯定是有一些原因的,先來(lái)看下在原碼中三級(jí)緩存放入的Lambda是什么:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
SmartInstantiationAwareBeanPostProcessor bp;
if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
for(Iterator var5 = this.getBeanPostProcessorCache().smartInstantiationAware.iterator(); var5.hasNext(); exposedObject = bp.getEarlyBeanReference(exposedObject, beanName)) {
bp = (SmartInstantiationAwareBeanPostProcessor)var5.next();
}
}
return exposedObject;
}從以上代碼可以執(zhí)行先把初始的 bean 對(duì)象賦給了 exposedObject ,然后如果發(fā)現(xiàn)這個(gè)bean 是否滿(mǎn)足mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()條件,進(jìn)而判斷當(dāng)前的bean是否是一個(gè)合成的代理對(duì)象。
在AOP中,合成代理對(duì)象是通過(guò)特定的機(jī)制(如JDK動(dòng)態(tài)代理或CGLIB動(dòng)態(tài)代理)創(chuàng)建的。這個(gè)條件可以用來(lái)檢查當(dāng)前創(chuàng)建的bean是否是這種合成的代理對(duì)象;
如果需要合成代理對(duì)象則 進(jìn)入exposedObject = bp.getEarlyBeanReference(exposedObject, beanName) 進(jìn)行代理對(duì)象的創(chuàng)建:
AbstractAutoProxyCreator.getEarlyBeanReference
// 省略代碼
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
Object exposedObject = bean;
try {
// 遞歸填充改bean 的其他屬性
this.populateBean(beanName, mbd, instanceWrapper);
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
} catch (Throwable var18) {
if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
throw (BeanCreationException)var18;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
}
// 省略代碼
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
} else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
} else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} else {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
} else {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
}從上面代碼可以看到 如果這個(gè)對(duì)象在緩存中沒(méi)有而且是必須要進(jìn)行依賴(lài)注入的, 會(huì)對(duì)實(shí)例化化后的改對(duì)象,使用this.createProxy 為其生產(chǎn)代理對(duì)象并進(jìn)行返回;所以使用了這個(gè)Lambda表達(dá)式目的就是返回一個(gè)普通對(duì)象的bean 或者代理對(duì)象的bean,那么為什么不直接在bean 實(shí)例化之后,就直接調(diào)用getEarlyBeanReference 方法,這樣將生成的普通對(duì)象或者代理對(duì)象的bean 直接放入到二級(jí)緩存中,這樣豈不是更為直接,顯然這樣做也是可以解決spring 的循環(huán)依賴(lài)的問(wèn)題,而且在二級(jí)緩存中存放的對(duì)象就是普通對(duì)象或者代理生成的對(duì)象。
雖然可以這樣做但是違反了spring 對(duì)代理對(duì)象生成的原則,Spring 的設(shè)計(jì)原則是盡可能保證普通對(duì)象創(chuàng)建完成之后,再生成其 AOP 代理(盡可能延遲代理對(duì)象的生成),因?yàn)檫@樣做的話(huà),所有代理都提前到了實(shí)例化之后,初始化階段前,顯然與盡可能延遲代理對(duì)象的生成 原則是違背的。所以在此使用 Lambda表達(dá)式 ,在真正需要?jiǎng)?chuàng)建對(duì)象bean 的提前引用時(shí),才通過(guò) Lambda表達(dá)式 來(lái)進(jìn)行創(chuàng)建 ,來(lái)遵循盡可能延遲代理對(duì)象的生成 原則。
沒(méi)有依賴(lài),有AOP 這種情況中,我們知道 AOP 代理對(duì)象的生成是在成品對(duì)象創(chuàng)建完成之后創(chuàng)建的,這也是 Spring 的設(shè)計(jì)原則,代理對(duì)象盡量推遲創(chuàng)建,循環(huán)依賴(lài) + AOP 這種情況中, 代理對(duì)象的生成提前了,因?yàn)楸仨氁WC其 AOP 功能,那么在bean 初始化完成之后,又到了要對(duì)改對(duì)象進(jìn)行代理增強(qiáng)的環(huán)節(jié),此時(shí)spring 又是怎么判斷改bean 已經(jīng)被增強(qiáng)為代理對(duì)象,而不需要重新創(chuàng)建代理對(duì)象?
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return this.wrapIfNecessary(bean, beanName, cacheKey);
}
從以上源碼中可以看到 在postProcessAfterInitialization bean 被初始化完成之后執(zhí)行的方法 從this.getCacheKey 獲取到的cacheKey ,最后比較兩個(gè)bean 是否是一個(gè),如果是則說(shuō)明bean 已經(jīng)進(jìn)行過(guò)代理,否則則重新執(zhí)行wrapIfNecessary 生成代理對(duì)象;
2.2 三級(jí)緩存工作過(guò)程
既然在spring 容器中bean 是單例的,那么就不可能存在改bean 的多個(gè)對(duì)象,也即對(duì)bean 的所有引用都指向同一個(gè)對(duì)象;此時(shí)就有一個(gè)問(wèn)題,當(dāng)一個(gè)bean 被依賴(lài)注入時(shí),怎么知道這個(gè)單例的bean 是否已經(jīng)被初始化?
所以就需要將已經(jīng)完成初始化的bean 放入到一個(gè)地方中,這個(gè)地方要滿(mǎn)足如果這個(gè)bean 已經(jīng)被初始化過(guò)了,則不需要進(jìn)行在進(jìn)行初始化,而且存和取都比較方便,spring 選用map 來(lái)對(duì)其進(jìn)行存儲(chǔ),其中key 為bean的那么,value 為單列bean:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
當(dāng)Spring創(chuàng)建一個(gè)單例bean時(shí),會(huì)先檢查一級(jí)緩存(singletonObjects),如果在其中找到bean實(shí)例,則直接返回。如果沒(méi)有找到,則繼續(xù)執(zhí)行bean的創(chuàng)建流程,對(duì)改bean 進(jìn)行實(shí)例化,如果改bean允許早期暴露則將獲取改對(duì)象的一個(gè)方法即將工廠方法存儲(chǔ)在三級(jí)緩存(singletonFactories)中。
在創(chuàng)建bean 如aservice的屬性注入過(guò)程中,如果發(fā)現(xiàn)還依賴(lài)了其它的bean,如:bservice 則進(jìn)入到bservice 的創(chuàng)建過(guò)程:首先對(duì)bservice 進(jìn)行實(shí)例化(如果bservice也運(yùn)行早期暴露,則也會(huì)被加入到第三級(jí)緩存中)、屬性注入、初始化;
如果此時(shí)bservice 中依賴(lài)了aservice 的bean,出現(xiàn)循環(huán)依賴(lài)問(wèn)題,先從一級(jí)緩存中獲取aservice 的bean,此時(shí)aservice的bean 是正在被創(chuàng)建的狀態(tài),則從二級(jí)緩存找 Spring會(huì)先嘗試從二級(jí)緩存(earlySingletonObjects)中獲取bean實(shí)例的早期引用,以解決循環(huán)依賴(lài),如果二級(jí)緩存中aservice 的早起引用不存在;則到三級(jí)緩存中,執(zhí)行aservice bean 的lamma 表達(dá)式,獲取到aservice 的普通對(duì)象/代理對(duì)象,并將其放入到第二級(jí)緩存,同時(shí)移除aservice 在第三級(jí)的緩存。
一旦bean創(chuàng)建完成,如:bservice 完成了依賴(lài)注入,初始化,aop(如果需要被代理)則會(huì)將其放入一級(jí)緩存(singletonObjects),并從二級(jí)緩存(earlySingletonObjects)和三級(jí)緩存(singletonFactories)中移除,即將bservice 放入到一級(jí)緩存(單例池中),同時(shí)從二級(jí)和三級(jí)緩存中移除bservice的早期應(yīng)用和 獲取早期引用的lamma 表達(dá)式。然后,在aservice 的屬性依賴(lài)注入中,就可以從一級(jí)緩存中獲取到bservice 的單例對(duì)象,進(jìn)行屬性注入,然后初始化,和aop(如果需要被aop代理),最后從二級(jí)和三級(jí)緩存中移除aservice的早期應(yīng)用和 獲取早期引用的lamma 表達(dá)式。
AbstractBeanFactory,doGetBean 獲取對(duì)應(yīng)的bean,此處只列舉關(guān)鍵代碼
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
/**
** 省略代碼
**/
// 會(huì)先檢查一級(jí)緩存(singletonObjects),如果在其中找到bean實(shí)例,則直接返回,
// 此方法調(diào)用DefaultSingletonBeanRegistry 下getSingleton 方法
// 方法(1) 從三級(jí)緩存中獲取對(duì)象
Object sharedInstance = this.getSingleton(beanName);
// 省略 sharedInstance 不為null 直接返回的代碼
/**
** 省略代碼
**/
// 如果沒(méi)有找到,則繼續(xù)執(zhí)行bean的創(chuàng)建流程,并將工廠方法存儲(chǔ)在三級(jí)緩存(singletonFactories)中
if (mbd.isSingleton()) {
// this.getSingleton 通過(guò)改bean 的工廠方法創(chuàng)建出來(lái)bean 并放入到單例池中
// 方法2:getSingleton(String beanName, ObjectFactory<?> singletonFactory) 創(chuàng)建對(duì)象
sharedInstance = this.getSingleton(beanName, () -> {
try {
// 創(chuàng)建普通對(duì)象/代理對(duì)象 并將工廠方法存儲(chǔ)在三級(jí)緩存(singletonFactories)中
// 此方法調(diào)用 AbstractAutowireCapableBeanFactory 下 createBean 方法然后在調(diào)用 doCreateBean 方法
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}方法(1) 從三級(jí)緩存中獲取對(duì)象 this.getSingleton(beanName) 方法:
Object sharedInstance = this.getSingleton(beanName) 方法,DefaultSingletonBeanRegistry 下getSingleton, 會(huì)先檢查一級(jí)緩存(singletonObjects),如果在其中找到bean實(shí)例,則直接返回:
@Nullable
public Object getSingleton(String beanName) {
return this.getSingleton(beanName, true);
}
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 從一級(jí)緩存獲取完整的bean
Object singletonObject = this.singletonObjects.get(beanName);
// 沒(méi)有獲取到 并且 這個(gè)bean 正在初始化中
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
// 從二級(jí)緩存獲取這個(gè)bean 的早期引用
singletonObject = this.earlySingletonObjects.get(beanName);
// 沒(méi)有早期引用 并且運(yùn)行循環(huán)依賴(lài)
if (singletonObject == null && allowEarlyReference) {
synchronized(this.singletonObjects) {
// 加對(duì)象鎖
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 從三級(jí)緩存 獲取bena 的 工廠類(lèi)
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 二級(jí)緩存中放入
this.earlySingletonObjects.put(beanName, singletonObject);
// 三級(jí)緩存中移除
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}單例池中沒(méi)有改bean 則進(jìn)入 sharedInstance = this.getSingleton(beanName, () -> {}) return this.createBean(beanName, mbd, args) 方法通過(guò)AbstractAutowireCapableBeanFactory.doCreateBean 創(chuàng)建普通對(duì)象/代理對(duì)象 并將工廠方法存儲(chǔ)在三級(jí)緩存(singletonFactories)中:
boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
}
// addSingletonFactory 放入到三級(jí)緩存
this.addSingletonFactory(beanName, () -> {
// 獲取提前暴露出來(lái)的bean 對(duì)象
return this.getEarlyBeanReference(beanName, mbd, bean);
});
}
try {
// 繼續(xù)對(duì)改bean 中的屬性進(jìn)行初始化
this.populateBean(beanName, mbd, instanceWrapper);
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
} catch (Throwable var18) {
if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
throw (BeanCreationException)var18;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
}
DefaultSingletonBeanRegistry.addSingletonFactory: bean的工廠放入到三級(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)) {
// 放入3級(jí)緩存map
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
// 將改bean 的名稱(chēng)放入到正在創(chuàng)建的bean 的set 集合
this.registeredSingletons.add(beanName);
}
}
}方法2:getSingleton(String beanName, ObjectFactory<?> singletonFactory) 創(chuàng)建對(duì)象:
此時(shí)的singletonFactory 為 this.getSingleton(beanName, () -> {
try {
// 創(chuàng)建普通對(duì)象/代理對(duì)象 并將工廠方法存儲(chǔ)在三級(jí)緩存(singletonFactories)中
// 此方法調(diào)用 AbstractAutowireCapableBeanFactory 下 createBean 方法然后在調(diào)用 doCreateBean 方法
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
})
方法中的lamma 表達(dá)式,通過(guò) singletonFactory.getObject() 就可以調(diào)用到 this.createBean(beanName, mbd, args) 方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized(this.singletonObjects) {
// 獲取對(duì)象鎖,然后從單例池中獲取bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 省略代碼
try {
// 獲取bean 的工廠
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
// 省略代碼
if (newSingleton) {
// bean 完成后放入到單例翅中
this.addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized(this.singletonObjects) {
// 單例池中放入改bean
this.singletonObjects.put(beanName, singletonObject);
// 從三級(jí)緩存和二級(jí)緩存中移除
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}案例過(guò)程:
在A 依賴(lài)于B,B 依賴(lài)于C,C又依賴(lài)A 的場(chǎng)景中,當(dāng)實(shí)例化 A后對(duì)A 的bean 對(duì)象進(jìn)行屬性依賴(lài)注入時(shí);發(fā)現(xiàn)其依賴(lài)了B;此時(shí)實(shí)例化 B,然后對(duì)B的bean 對(duì)象進(jìn)行屬性依賴(lài)注入;發(fā)現(xiàn)依賴(lài)了C 的bean, 實(shí)例化 C,對(duì)C 的bean 進(jìn)行屬性注入時(shí),發(fā)現(xiàn)依賴(lài)了A的bean :
此時(shí)在對(duì)C的bean依賴(lài)注入為:先從單例池(一級(jí)緩存獲取A的bean),因?yàn)锳的bean 還在被創(chuàng)建過(guò)程中,所以從單例池中的到的是null,在從二級(jí)緩存(早期暴露的對(duì)象)獲取A的早期暴露bean,如果二級(jí)緩存也沒(méi)有,則從三級(jí)緩存中執(zhí)行l(wèi)amma 表達(dá)式,獲取到A的早期暴露bean (將A的bean 放入到二級(jí)緩存中,并同時(shí)從三級(jí)緩存中移除A的bean 的lamma 表達(dá)式)進(jìn)行屬性依賴(lài)注入;然后完成C的依賴(lài)注入,初始化 將其放入到單例池中,然后完成B的屬性依賴(lài)注入,初始化,將其放入到單例池;完成A 的屬性依賴(lài)注入,初始化,并放入到單例池中;
過(guò)程步驟:
- 開(kāi)始初始化 A,創(chuàng)建 A 的實(shí)例并完成屬性注入;
- 當(dāng)A屬性注入 過(guò)程中發(fā)現(xiàn) A 依賴(lài)于 B,Spring 會(huì)嘗試從單例池中獲取B的ean,如果獲取不到則進(jìn)入到創(chuàng)建 B 的實(shí)例過(guò)程;
- 創(chuàng)建 B 的實(shí)例,對(duì)B 的bean進(jìn)行依賴(lài)注入,發(fā)現(xiàn) B 還有其他依賴(lài),如: C。此時(shí) Spring 會(huì)先從單例池中獲取C的bean,如果獲取不到則進(jìn)入到C的bean 創(chuàng)建過(guò)程;;
- 在C實(shí)例化后,對(duì)C屬性依賴(lài)注入 過(guò)程中,發(fā)現(xiàn) C 依賴(lài)于 A,從單例池中獲取A 的bean,此時(shí) A 實(shí)例還未創(chuàng)建完成,從二級(jí)緩存獲取A bean 的早期引用;如果還沒(méi)有,則從三級(jí)緩存找到A bean的lamma 表達(dá)式,獲取到Abean 的早期暴露對(duì)象,并將Abean的早期暴露對(duì)象,放入到二級(jí)緩存同時(shí),將Abean從三級(jí)緩存中移除;
- 繼續(xù)完成對(duì) C 屬性注入(如果有),然后進(jìn)行初始化,最終將C的bean 放入到單例池中,同時(shí)刪除其在二級(jí)和三級(jí)緩存的對(duì)象;
- 然后完成對(duì)B 其它屬性依賴(lài)注入中,發(fā)現(xiàn) B 不再依賴(lài)其他對(duì)象,完成 B 的初始化,最終將B的bean 放入到單例池中,同時(shí)刪除其在二級(jí)和三級(jí)緩存的對(duì)象;
- 繼續(xù) A的依賴(lài)注入,在 A的依賴(lài)注入 過(guò)程中,發(fā)現(xiàn) A 依賴(lài)于 B,B 的實(shí)例早已創(chuàng)建完成,因此可以直接從一級(jí)緩存獲取 B 的實(shí)例;
- 完成 A 的依賴(lài)注入,初始化 ,最終將A的bean 放入到單例池中,同時(shí)刪除其在二級(jí)和三級(jí)緩存的對(duì)象
Spring 使用了緩存機(jī)制,確保在遞歸調(diào)用時(shí)能夠正確地獲取到已經(jīng)創(chuàng)建的對(duì)象實(shí)例,避免死循環(huán)。同時(shí),Spring 也會(huì)處理代理對(duì)象的生成和使用,以確保 A、B、C 的代理對(duì)象在正確的時(shí)間被創(chuàng)建和使用。
三、Spring 三級(jí)緩存無(wú)法解決的單例循環(huán)依賴(lài)情況
3.1 通過(guò)構(gòu)造方法注入的bean ,出現(xiàn)循環(huán)依賴(lài)會(huì)報(bào)錯(cuò)
在A類(lèi)中通過(guò)構(gòu)造方法的方式注入B的bean,在B類(lèi)中通過(guò)構(gòu)造方法的方式注入A的bean;在此場(chǎng)景中,
- 在調(diào)用A的構(gòu)造方法進(jìn)行實(shí)例化時(shí),發(fā)現(xiàn)依賴(lài)的B的bean,需要對(duì)B類(lèi)進(jìn)行實(shí)例化;
- 調(diào)用B類(lèi)的構(gòu)造方法進(jìn)行實(shí)例化時(shí),發(fā)現(xiàn)依賴(lài)的A的bean,此時(shí)出現(xiàn)循環(huán)依賴(lài);
- 然后此時(shí)需要對(duì)A的bean 提前進(jìn)行引用的暴露;
- 然而在對(duì)A的bean 提前進(jìn)行引用的暴露,需要用到A 的實(shí)例化對(duì)象,此時(shí)A的實(shí)例化對(duì)象還沒(méi)有被創(chuàng)建,則直接報(bào)錯(cuò);
3.2 早期暴露的非aop代理對(duì)象引用,出現(xiàn)循環(huán)依賴(lài)會(huì)報(bào)錯(cuò)
@Service
public class AserviceImpl implements Aservice {
@Autowired
private Bservice bservice;
@Async
public void test(){
System.out.println(bservice);
}
}
@Service
public class BserviceImpl implements Bservice {
@Autowired
private Aservice aservice;
}當(dāng)使用 @Async 注解標(biāo)注一個(gè)bean 中的方法為異步方法時(shí),Bservice 中注入的Aservice aservice 的bean 與最終生成的Aservice 的bean 不相同而導(dǎo)致報(bào)錯(cuò);
- 對(duì)Aservice 進(jìn)行實(shí)例化后,對(duì)其bservice 屬性進(jìn)行初始化;
- 對(duì)Bservice 進(jìn)行實(shí)例化,然后對(duì)其aservice 屬性進(jìn)行初始化,此時(shí)發(fā)現(xiàn)循環(huán)依賴(lài);
- 暴露Aservice 的bean 到二級(jí)緩存中,因?yàn)锳service 非aop 代理對(duì)象 ,所以此時(shí)二級(jí)緩存中放入的是Aservice 的普通對(duì)象;
- Bservice 的bean 完成初始化;
- Aservice 對(duì) bservice 屬性初始化完成 ,并將Aservice 的bean 放入到一級(jí)緩存,并從二級(jí)緩存中刪除;
- 對(duì)Aservice 的bean 進(jìn)行代理對(duì)象的包裝,包裝后的bean 與之前放入到一級(jí)緩存的bean 兩個(gè)不是同一個(gè),程序報(bào)錯(cuò);
四、Lazy 注解解決循環(huán)依賴(lài)問(wèn)題
延遲加載可以通過(guò)將 bean 的依賴(lài)關(guān)系運(yùn)行時(shí)進(jìn)行注入,而不是在初始化階段。這樣,當(dāng)遇到循環(huán)依賴(lài)時(shí),Spring 可以先創(chuàng)建需要的 bean 實(shí)例,并將其設(shè)置為代理對(duì)象,而不需要立即解決依賴(lài)關(guān)系。
@Service
public class AserviceImpl implements Aservice {
@Autowired
@Lazy
private Bservice bservice;
@Async
public void test(){
}
}Lazy 延遲加載打破循環(huán)依賴(lài); 通過(guò)其它途徑生成bservice 的lazy 的代理對(duì)象,不會(huì)去走創(chuàng)建bservice 的代理 對(duì)象 然后注入aservice 這套流程。這樣創(chuàng)建aservice 的單例對(duì)象并放入到單例池中,Bservice 的bean 在實(shí)例化后,注入aservice bean 屬性就可以從單例池中加載到aservice 的真正的bean ,而不會(huì)出現(xiàn)bean 對(duì)象不一致的問(wèn)題。
總結(jié)
spring 通過(guò)三級(jí)緩存解決單例的循環(huán)依賴(lài)問(wèn)題,singletonObjects 用來(lái)存放已經(jīng)初始化完成的bean,earlySingletonObjects 用來(lái)存放早期暴露出來(lái)的半成品bean 的引用,singletonFactories 用來(lái)存放獲取早期引用的 Lambda表達(dá)式 工廠。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot使用 druid 連接池來(lái)優(yōu)化分頁(yè)語(yǔ)句
這篇文章主要介紹了SpringBoot使用 druid 連接池來(lái)優(yōu)化分頁(yè)語(yǔ)句,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
java統(tǒng)計(jì)漢字字?jǐn)?shù)的方法示例
這篇文章主要介紹了java統(tǒng)計(jì)漢字字?jǐn)?shù)的方法,結(jié)合實(shí)例形式分析了java正則判定、字符串遍歷及統(tǒng)計(jì)相關(guān)操作技巧,需要的朋友可以參考下2017-05-05
springboot post接口接受json時(shí),轉(zhuǎn)換為對(duì)象時(shí),屬性都為null的解決
這篇文章主要介紹了springboot post接口接受json時(shí),轉(zhuǎn)換為對(duì)象時(shí),屬性都為null的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
關(guān)于bootstrap.yml和bootstrap.properties的優(yōu)先級(jí)問(wèn)題
這篇文章主要介紹了關(guān)于bootstrap.yml和bootstrap.properties的優(yōu)先級(jí)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java如何計(jì)算兩個(gè)時(shí)間段內(nèi)的工作日天數(shù)
這篇文章主要介紹了Java如何計(jì)算兩個(gè)時(shí)間段內(nèi)的工作日天數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
java實(shí)現(xiàn)Xml與json之間的相互轉(zhuǎn)換操作示例
這篇文章主要介紹了java實(shí)現(xiàn)Xml與json之間的相互轉(zhuǎn)換操作,結(jié)合實(shí)例形式分析了Java xml與json相互轉(zhuǎn)換工具類(lèi)的定義與使用相關(guān)操作技巧,需要的朋友可以參考下2019-06-06
SpringBoot中使用Cookie實(shí)現(xiàn)記住登錄的示例代碼
這篇文章主要介紹了SpringBoot中使用Cookie實(shí)現(xiàn)記住登錄的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
java 使用ImageIO.writer從BufferedImage生成jpeg圖像遇到問(wèn)題總結(jié)及解決
這篇文章主要介紹了java 使用ImageIO.writer從BufferedImage生成jpeg圖像遇到問(wèn)題總結(jié)及解決的相關(guān)資料,需要的朋友可以參考下2017-03-03

