解析Spring中的循環(huán)依賴問題:再探三級緩存(AOP)
前言
在之前的內(nèi)容中,我們簡要探討了循環(huán)依賴,并指出僅通過引入二級緩存即可解決此問題。然而,你可能會好奇為何在Spring框架中還需要引入三級緩存singletonFactories。在前述總結(jié)中,我已經(jīng)提供了答案,即AOP代理對象。接下來,我們將深入探討這一話題。
AOP
在Spring框架中,AOP的實現(xiàn)是通過一個名為BeanPostProcessor的類完成的,其中一個關(guān)鍵的BeanPostProcessor就是AnnotationAwareAspectJAutoProxyCreator。值得一提的是,該類的父類是AbstractAutoProxyCreator。在Spring的AOP機制中,通常會使用JDK動態(tài)代理或者CGLib動態(tài)代理來實現(xiàn)代理對象的生成。因此,如果在某個類的方法上設(shè)置了切面,那么最終這個類將需要生成一個代理對象來應(yīng)用AOP的功能。
一般的執(zhí)行流程通常是這樣的:A類--->生成一個普通對象-->屬性注入-->基于切面生成一個代理對象-->將該代理對象存入singletonObjects單例池中。
而AOP可以說是Spring框架中除了IOC之外的另一個重要功能,而循環(huán)依賴則屬于IOC的范疇。因此,為了讓這兩個重要功能同時存在于Spring框架中,Spring需要進行特殊處理。
三級緩存
在處理這種情況時,Spring框架利用了第三級緩存singletonFactories。下面我們來看一下關(guān)于三級緩存的源代碼實現(xiàn):
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
首先,singletonFactories中存儲的是某個beanName對應(yīng)的ObjectFactory。在bean的生命周期中,生成完原始對象之后,Spring框架會構(gòu)造一個ObjectFactory并將其存入singletonFactories中。這個ObjectFactory是一個函數(shù)式接口,因此支持Lambda表達式,形式為() -> getEarlyBeanReference(beanName, mbd, bean)。為了更清晰地理解這個過程,我提供一張圖片。
圖片
getEarlyBeanReference
在上述Lambda表達式中,它實際上代表了一個ObjectFactory,執(zhí)行該Lambda表達式將會調(diào)用getEarlyBeanReference方法。下面是getEarlyBeanReference方法的實現(xiàn):
//AbstractAutowireCapableBeanFactory
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
在整個Spring框架中,值得注意的是,只有AbstractAutoProxyCreator這個類在實現(xiàn)getEarlyBeanReference方法時才具有真正的意義。這個類專門用于處理AOP(面向切面編程)。
// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
那么,getEarlyBeanReference方法的具體操作是什么呢? 首先,它會獲取一個cachekey,這個cachekey實際上就是beanName。 接著,它會將beanName和bean(即原始對象)存儲到earlyProxyReferences中。 接下來,它會調(diào)用wrapIfNecessary方法進行AOP操作,這將生成一個代理對象。
那么,什么時候會調(diào)用getEarlyBeanReference方法呢?讓我們再次回到循環(huán)依賴的場景中。
圖片
image
在我上一節(jié)的基礎(chǔ)上,我增加了兩句話,以便更好地理解觸發(fā)緩存機制以解決AOP代理對象生成的時機。
一旦原始對象通過構(gòu)造方法生成后,會被存儲到三級緩存中,并且會與一個lambda表達式關(guān)聯(lián)。然而,在這個階段,它并不會被執(zhí)行。
一旦BBean需要ABean時,系統(tǒng)會首先查看三級緩存以確定是否存在緩存。如果存在緩存,則lambda表達式將會被執(zhí)行,其代碼已在前面展示過。該lambda表達式的目的是將代理對象放入earlySingletonObjects中。需要注意的是,此時代理對象并未被放入singletonObjects中。那么代理對象何時會被放入singletonObjects中呢?
這個時候你可能已經(jīng)明白了earlySingletonObjects的用途。由于只獲取了A原始對象的代理對象,這個代理對象并不完整,因為A原始對象尚未進行屬性填充。因此,在這種情況下,我們不能直接將A的代理對象放入singletonObjects中。因此,我們只能將代理對象放入earlySingletonObjects,這樣依次類推。
在Spring框架中,在循環(huán)依賴場景下,當(dāng)Bean B創(chuàng)建完成后,Bean A繼續(xù)其生命周期。在Bean A完成屬性注入后,根據(jù)其自身邏輯進行AOP操作。此時,我們知道Bean A的原始對象已經(jīng)經(jīng)歷了AOP處理,因此對于Bean A本身而言,不需要再次進行AOP。那么,如何確定一個對象是否已經(jīng)經(jīng)歷了AOP呢?
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
沒錯,這個earlyProxyReferences確實提前緩存了對象是否已經(jīng)被代理過,這樣就避免了重復(fù)的AOP處理。
舉例反證
那么問題來了,當(dāng)A對象創(chuàng)建時,它可以是原始對象,但當(dāng)B對象創(chuàng)建時,卻成功創(chuàng)建了A的代理對象。然后再回頭給A對象進行屬性注入和初始化,這些操作似乎與代理對象無關(guān)?
這個問題涉及到了 Spring 中動態(tài)代理的實現(xiàn)。無論是使用cglib代理還是jdk動態(tài)代理生成的代理類,代理時都會將目標(biāo)對象 target 保存在最終生成的代理 $proxy 中。你可以將代理對象看作是對原始對象地址的一層包裝,最終仍然會回到原始對象上。因此,對原始bean的進一步完善實際上也就是對代理對象的完善。
還有一個需要注意的問題,當(dāng)A創(chuàng)建時,由于earlyProxyReferences緩存的原因,并沒有創(chuàng)建代理對象,因此此時A仍然保持為原始對象。我們知道,當(dāng)bean創(chuàng)建完成后,它將被放入一級緩存中,但如果在此之后被其他對象引用,那不就會出現(xiàn)問題嗎?別人引用的都是原始對象了,而不是代理對象,但是請不要著急,因為在實例化之后,有一行代碼可以解決這個問題。
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
......省略代碼
在實例化原始對象后,他會首先從三級緩存中檢查是否存在緩存對象。這是因為在創(chuàng)建B對象時,已經(jīng)將A的代理對象放入二級緩存。因此,取出的對象是代理對象。接著,當(dāng)進行 exposedObject == bean 的比較時,發(fā)現(xiàn)它們不相同。因此,以代理對象為準(zhǔn)并將其返回。最終,最外層存儲的將是代理對象。
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
總結(jié)
在上一個章節(jié)中我們提到了今天要討論三級緩存,讓我們根據(jù)上面提到的三級緩存內(nèi)容,做一個詳盡的總結(jié):
- singletonObjects:緩存經(jīng)過了完整生命周期的bean。
- earlySingletonObjects緩存了未經(jīng)過完整生命周期的bean。當(dāng)某個bean出現(xiàn)循環(huán)依賴時,該bean會被提前放入earlySingletonObjects中。如果該bean需要經(jīng)過AOP,那么代理對象將會被放入earlySingletonObjects;否則,原始對象將被放入其中。然而,無論是代理對象還是原始對象,它們的生命周期都尚未完全結(jié)束。因此視為未經(jīng)過完整生命周期的bean。
- singletonFactories緩存的是一個ObjectFactory,這個ObjectFactory實際上是一個Lambda表達式。在每個Bean的生成過程中,當(dāng)原始對象實例化完成后,會提前基于原始對象生成一個Lambda表達式,并將其保存到三級緩存中。這個Lambda表達式可能會被使用,也可能不會。如果當(dāng)前Bean不存在循環(huán)依賴,那么這個Lambda表達式將不會被使用,當(dāng)前的Bean將按照正常的生命周期執(zhí)行完畢,并將自身放入singletonObjects中。但是,如果在依賴注入的過程中發(fā)現(xiàn)了循環(huán)依賴(即當(dāng)前正在創(chuàng)建的Bean被其他Bean所依賴),則會從三級緩存中取出Lambda表達式,并執(zhí)行它以獲取一個對象,然后將得到的對象放入二級緩存中。需要特別注意的是,如果當(dāng)前Bean需要AOP處理,則執(zhí)行Lambda表達式后得到的將是代理對象;否則,直接得到的是原始對象。
當(dāng)涉及Spring框架中動態(tài)代理的實現(xiàn)機制時,除了已經(jīng)提到的earlySingletonObjects和singletonFactories這兩個緩存外,還有一個重要的緩存值得一提,那就是earlyProxyReferences。這個緩存的作用在于記錄某個原始對象是否已經(jīng)進行過AOP(面向切面編程)處理。
至此,整個循環(huán)依賴解決完畢。