1、弄清楚什么是循環(huán)依賴?Spring的循環(huán)依賴是如何產(chǎn)生的?
2、Spring的循環(huán)依賴底層實現(xiàn)邏輯
3、Spring的三級緩存,每一級緩存分別干了啥,解決什么問題
4、Spring二級緩存能否搞定循環(huán)依賴?
5、Spring循環(huán)依賴什么場景搞不定
就是A對象依賴了B對象,B對象依賴了A對象。
比如:
// A依賴了B
class A{public B b;
}
// B依賴了A
class B{public A a;
}
1、那么循環(huán)依賴是個問題嗎?如果不考慮Spring,循環(huán)依賴并不是問題,因為對象之間相互依賴是很正常的事情。
比如
A a = new A();
B b = new B();
a.b = b;
b.a = a;
這樣,A,B就依賴上了。
因為,在Spring中,一個對象并不是簡單new出來了,而是會經(jīng)過一系列的Bean的生命周期,就是因為Bean的生命周期所以才會出現(xiàn)循環(huán)依賴問題。
當然,在Spring中,出現(xiàn)循環(huán)依賴的場景很多,有的場景Spring自動幫我們解決了,而有的場景則需要程序員來解決。
要明白Spring中的循環(huán)依賴,得先明白Spring中Bean的生命周期。
這里不會對Bean的生命周期進行詳細的描述,只描述一下大概的過程。
PS:如需詳細了解,可以點擊傳送門:Spring Bean生命周期文章來了解Spring Bean生命周期詳細流程。
被Spring管理的對象叫做Bean。Bean的生成步驟如下:
1、Spring掃描class得到BeanDefinition
2、根據(jù)得到的BeanDefinition去生成bean
3、首先根據(jù)class推斷構(gòu)造方法
4、根據(jù)推斷出來的構(gòu)造方法,反射,得到一個對象(暫時叫做原始對象)
5、填充原始對象中的屬性(依賴注入)
6、如果原始對象中的某個方法被AOP了,那么則需要根據(jù)原始對象生成一個代理對象
7、把最終生成的代理對象放入單例池(源碼中叫做singletonObjects)中,下次getBean時就直接從單例池拿即可
可以看到,對于Spring中的Bean的生成過程,步驟還是很多的,并且不僅僅只有上面的7步,還有很多很多,比如Aware回調(diào)、初始化等等,這里不詳細討論。
可以發(fā)現(xiàn),在Spring中,構(gòu)造一個Bean,包括了new這個步驟(第4步構(gòu)造方法反射)。
得到一個原始對象后,Spring需要給對象中的屬性進行依賴注入
比如上文說的A類,A類中存在一個B類的b屬性,
所以,
1、當A類生成了一個原始對象之后,就會去給b屬性去賦值,
2、此時就會根據(jù)b屬性的類型和屬性名去BeanFactory中去獲取B類所對應的單例bean。
3、如果此時BeanFactory中存在B對應的Bean,那么直接拿來賦值給b屬性;
4、如果此時BeanFactory中不存在B對應的Bean,則需要生成一個B對應的Bean,然后賦值給b屬性。
問題就出現(xiàn)在第二種情況,
如果此時B類在BeanFactory中還沒有生成對應的Bean,那么就需要去生成,就會經(jīng)過B的Bean的生命周期。
那么在創(chuàng)建B類的Bean的過程中,如果B類中存在一個A類的a屬性,那么在創(chuàng)建B的Bean的過程中就需要A類對應的Bean,
但是,觸發(fā)B類Bean的創(chuàng)建的條件是A類Bean在創(chuàng)建過程中的依賴注入,所以這里就出現(xiàn)了循環(huán)依賴:
ABean創(chuàng)建–>依賴了B屬性–>觸發(fā)BBean創(chuàng)建—>B依賴了A屬性—>需要ABean(但ABean還在創(chuàng)建過程中)
從而導致ABean創(chuàng)建不出來,BBean也創(chuàng)建不出來。
這是循環(huán)依賴的場景,但是上文說了,在Spring中,通過某些機制幫開發(fā)者解決了部分循環(huán)依賴的問題,這個機制就是三級緩存。
在了解為什么spring 循環(huán)依賴需要緩存之前,需要對aop有一個大概的了解
AOP就是通過一個BeanPostProcessor來實現(xiàn)的,這個BeanPostProcessor就是AnnotationAwareAspectJAutoProxyCreator,它的父類是AbstractAutoProxyCreator。
而在Spring中AOP利用的要么是JDK動態(tài)代理,要么CGLib的動態(tài)代理,所以如果給一個類中的某個方法設置了切面,那么這個類最終就需要生成一個代理對象。
一般過程就是:
A類—>生成一個普通對象–>屬性注入–>基于切面生成一個代理對象–>把代理對象放入singletonObjects單例池中。
而AOP可以說是Spring中除開IOC的另外一大功能,而循環(huán)依賴又是屬于IOC范疇的,所以這兩大功能想要并存,Spring需要特殊處理。
如何處理的,就是利用了第三級緩存singletonFactories。
這里先有個簡單印象,三級緩存是通用的叫法。
一級緩存為:singletonObjects
二級緩存為:earlySingletonObjects
三級緩存為:singletonFactories
先稍微解釋一下這三個緩存的作用,后面詳細分析:
singletonObjects中緩存的是已經(jīng)經(jīng)歷了完整生命周期的bean對象。
earlySingletonObjects比singletonObjects多了一個early,表示緩存的是早期的bean對象。
早期是什么意思?表示Bean的生命周期還沒走完就把這個Bean放入了earlySingletonObjects。
singletonFactories中緩存的是ObjectFactory,表示對象工廠,表示用來創(chuàng)建早期bean對象的工廠。
先來分析為什么緩存能解決循環(huán)依賴。
上文分析得到,之所以產(chǎn)生循環(huán)依賴的問題,主要是:
A創(chuàng)建時—>需要B---->B去創(chuàng)建—>需要A,從而產(chǎn)生了循環(huán)
那么如何打破這個循環(huán),加個中間人(緩存)
A的Bean在創(chuàng)建過程中,在進行依賴注入之前,
1、先把A的原始Bean放入緩存(提早暴露,只要放到緩存了,其他Bean需要時就可以從緩存中拿了)
2、放入緩存后,再進行依賴注入,此時A的Bean依賴了B的Bean,如果B的Bean不存在,則需要創(chuàng)建B的Bean,
3、而創(chuàng)建B的Bean的過程和A一樣,也是先創(chuàng)建一個B的原始對象,然后把B的原始對象提早暴露出來放入緩存中
4、然后在對B的原始對象進行依賴注入A,此時能從緩存中拿到A的原始對象(雖然是A的原始對象,還不是最終的Bean),B的原始對象依賴注入完了之后,B的生命周期結(jié)束,那么A的生命周期也能結(jié)束。
因為整個過程中,都只有一個A原始對象,所以對于B而言,就算在屬性注入時,注入的是A原始對象,也沒有關系,因為A原始對象在后續(xù)的生命周期中在堆中沒有發(fā)生變化。
從上面這個分析過程中可以得出,只需要一個緩存就能解決循環(huán)依賴了,那么為什么Spring中還需要singletonFactories呢?
基于上面的場景想一個問題:
如果A的原始對象注入給B的屬性之后,A的原始對象進行了AOP產(chǎn)生了一個代理對象,
此時就會出現(xiàn),對于A而言,它的Bean對象其實應該是AOP之后的代理對象,而B的a屬性對應的并不是AOP之后的代理對象,這就產(chǎn)生了沖突。
大概是這么個過程
創(chuàng)建A-》填充B-》創(chuàng)建B-》填充A-》從singletonFactories獲取A-》B生命周期結(jié)束-》A初始化前和初始化-》進行AOP-》A放入單例池
經(jīng)過上面的步驟后
B依賴的A和最終的A不是同一個對象。
下面這個方法是在填充屬性之前,會進行addSingletonFactory(beanName, () ->getEarlyBeanReference(beanName, mbd, bean));
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) { if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () ->getEarlyBeanReference(beanName, mbd, bean));
}
其實就是把當前類A對象緩存到這個三級緩存里,因為這個對象如果實現(xiàn)了AOP,在后續(xù)他會從一個原始對象變成一個aop對象,所以采用了函數(shù)式接口,getEarlyBeanReference就是處理到底返回原始對象還是AOP的代理對象。
singletonFactories(三級緩存)中存的是某個beanName對應的ObjectFactory,在bean的生命周期中,生成完原始對象之后,就會構(gòu)造一個ObjectFactory存入singletonFactories中。
這個ObjectFactory是一個函數(shù)式接口,所以支持Lambda表達式:() ->getEarlyBeanReference(beanName, mbd, bean)
上面的Lambda表達式就是一個ObjectFactory,執(zhí)行該Lambda表達式就會去執(zhí)行getEarlyBeanReference方法,而該方法如下:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
該方法會去執(zhí)行SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference方法,而這個接口下的實現(xiàn)類中只有兩個類實現(xiàn)了這個方法,一個是AbstractAutoProxyCreator,一個是InstantiationAwareBeanPostProcessorAdapter,它的實現(xiàn)如下:
InstantiationAwareBeanPostProcessorAdapter的實現(xiàn)
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {return bean;
}
AbstractAutoProxyCreator的實現(xiàn)
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
該方法主要做了幾件事情
1、首先得到一個cachekey,cachekey就是beanName。
2、然后把beanName和bean(這是原始對象)存入earlyProxyReferences中
3、調(diào)用wrapIfNecessary進行AOP,得到一個代理對象。
在整個Spring中,默認就只有AbstractAutoProxyCreator真正意義上實現(xiàn)了getEarlyBeanReference方法,而該類就是用來進行AOP的。
前面提到的AnnotationAwareAspectJAutoProxyCreator的父類就是AbstractAutoProxyCreator。
只有當出現(xiàn)了循環(huán)依賴的場景時才會去調(diào)用?為什么
如果調(diào)用的是C或者其他bean,其他bean并沒有和他循環(huán)依賴,這個時候getbean的時候 是可以從一級緩存里拿到的,
只有因為此時出現(xiàn)了循環(huán)依賴,A對象還沒有走完bean生命周期的,所以一級緩存里拿不到。
先想想現(xiàn)在的場景
1、A實例化-》添加三級緩存-》屬性填充B
2.1、B去創(chuàng)建對象,實例化
2.2、B添加三級緩存
2.3、屬性填充A(此時A的生命周期還沒有走完)-》一級緩存-》二級緩存-》三級緩存-》存到二級緩存
2.4、剩下的生命周期流程...
2、AOP(這里還有個問題需要處理,就是前面A已經(jīng)執(zhí)行了lamdba表達式,生成了AOP代理對象,這里無需再次執(zhí)行AOP)
3、A的剩下生命周期流程…
1、支持循環(huán)依賴的情況下,就會提前增加三級緩存*()
addSingletonFactory(beanName, () ->getEarlyBeanReference(beanName, mbd, bean));
2、在屬性填充時,每個填充的對象,都會先調(diào)用getBean,而getBean則會先調(diào)用getSingleton()方法
Object sharedInstance = getSingleton(beanName);
而在這個方法里面就會看到調(diào)用getEarlyBeanReference方法的地方
下面這個方法主要就是判斷一級緩存和二級緩存都不存在的情況下,根據(jù)beanName獲取三級緩存
調(diào)用singletonFactory.getObject()方法,他底層就是調(diào)用前面的lamdbabia表達式方法,生成原始或者代理對象,放到二級緩存,刪除掉三級緩存
解決循環(huá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) { // 調(diào)用getEarlyBeanReference方法的地方
singletonObject = singletonFactory.getObject();
// 添加二級緩存
this.earlySingletonObjects.put(beanName, singletonObject);
// 刪除三級緩存
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
注意這里還有個需要注意的點,在執(zhí)行這個方法時,會緩存當前bean到earlyProxyReferences里,他主要是用來標記這個bean是否進行過aop
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
初始化后部分源碼
可以看到他會先判斷earlyProxyReferences是否存在,以此來判斷是否進行過了aop
@Override
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;
}
通過上面代碼可以看到,如果提前進行過aop,他是直接返回了原始對象,并沒有緩存到單例池
答案是初始化后,回去處理,源碼如下
如果進行了循環(huán)依賴,肯定會進下面的if
會再次獲取下當前bean,然后判斷傳當前bean和初始化后的bean是否相等,是的話,就證明是同一個原始對象,就把剛從二級緩存取到的代理對象進行賦值,并且最終返回
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);
SetactualDependentBeans = 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.");
}
}
}
}
作用:不論是spring源碼還是工作中經(jīng)常用到getBean(),都是從這個緩存單例池拿對象
值從哪里來的?經(jīng)歷了完整的聲明周期會存放進來
緩存經(jīng)過了完整生命周期的bean
作用:他主要是解決在循環(huán)依賴過程中,保持對象的單例。
值從哪里來的?三級緩存執(zhí)行了lambda表達式后,生成的對象會放入二級緩存
原理解析
緩存未經(jīng)過完整生命周期的bean,
如果某個bean出現(xiàn)了循環(huán)依賴,就會提前把這個暫時未經(jīng)過完整生命周期的bean放入earlySingletonObjects中,
這個bean如果要經(jīng)過AOP,那么就會把代理對象放入earlySingletonObjects中,
否則就是把原始對象放入earlySingletonObjects,
但是不管怎么樣,就是是代理對象,代理對象所代理的原始對象也是沒有經(jīng)過完整生命周期的,所以放入earlySingletonObjects我們就可以統(tǒng)一認為是未經(jīng)過完整生命周期的bean。
作用:打破循環(huán)依賴的關鍵!
值從哪里來的?實例化后,存放
原理解析
緩存的是一個ObjectFactory,也就是一個Lambda表達式。
1、在每個Bean的生成過程中,經(jīng)過實例化得到一個原始對象后,都會提前基于原始對象暴露一個Lambda表達式,并保存到三級緩存中,
2、這個Lambda表達式可能用到,也可能用不到,如果當前Bean沒有出現(xiàn)循環(huán)依賴,那么這個Lambda表達式?jīng)]用,
當前bean按照自己的生命周期正常執(zhí)行,執(zhí)行完后直接把當前bean放入singletonObjects中,
3、如果當前bean在依賴注入時發(fā)現(xiàn)出現(xiàn)了循環(huán)依賴(當前正在創(chuàng)建的bean被其他bean依賴了),
3.1、則從三級緩存中拿到Lambda表達式,并執(zhí)行Lambda表達式得到一個對象,并把得到的對象放入二級緩存
3.2、如果當前Bean**需要AOP,**那么執(zhí)行l(wèi)ambda表達式,得到就是對應的代理對象,
3.3.、如果無需AOP,則直接得到一個原始對象。
其實還要一個緩存,就是earlyProxyReferences,它用來記錄某個原始對象是否進行過AOP了。
原因:單例可以是因為有單例池,可以找到單例bean,原型Bean則沒辦法像單例那樣處理
如果只有有一個是單例的,就可以解決,無非就是多循環(huán)幾圈
原因:構(gòu)造方法寫死了依賴其他對象,這時根本無法創(chuàng)建實例
@Lazy注解即可
原因:是因為,他會生成一個代理對象,可以規(guī)避掉無法創(chuàng)建實例的問題。
先反向分析一下singletonFactories
8.1、為什么需要singletonFactories?假設沒有singletonFactories,只有earlySingletonObjects,earlySingletonObjects是二級緩存,它內(nèi)部存儲的是為經(jīng)過完整生命周期的bean對象,Spring原有的流程是出現(xiàn)了循環(huán)依賴的情況下:
1、先從singletonFactories中拿到lambda表達式,這里肯定是能拿到的,因為每個bean實例化之后,依賴注入之前,就會生成一個lambda表示放入singletonFactories中
2、執(zhí)行l(wèi)ambda表達式,得到結(jié)果,將結(jié)果放入earlySingletonObjects中
?
?
首先,將原始對象或AOP之后的代理對象放入earlySingletonObjects中的有兩種:
1、實例化之后,依賴注入之前:如果是這樣,那么對于每個bean而言,都是在依賴注入之前會去進行AOP,這是不符合bean生命周期步驟的設計的。
2、真正發(fā)現(xiàn)某個bean出現(xiàn)了循環(huán)依賴時:
按現(xiàn)在Spring源碼的流程來說,就是getSingleton(String beanName, boolean allowEarlyReference)中,是在這個方法中判斷出來了當前獲取的這個bean在創(chuàng)建中,就表示獲取的這個bean出現(xiàn)了循環(huán)依賴,
那在這個方法中該如何拿到原始對象呢?
更加重要的是,該如何拿到AOP之后的代理對象呢?
難道在這個方法中去循環(huán)調(diào)用BeanPostProcessor的初始化后的方法嗎?不是做不到,不太合適,代碼太丑。
最關鍵的是在這個方法中該如何拿到原始對象呢?
還是得需要一個Map,預先把這個Bean實例化后的對象存在這個Map中,那這樣的話還不如直接用第一種方案,但是第一種又直接打破了Bean生命周期的設計。
?
所以,我們可以發(fā)現(xiàn),現(xiàn)在Spring所用的singletonFactories,為了調(diào)和不同的情況,
在singletonFactories中存的是lambda表達式,這樣的話,只有在出現(xiàn)了循環(huán)依賴的情況,才會執(zhí)行l(wèi)ambda表達式,才會進行AOP,
也就說只有在出現(xiàn)了循環(huán)依賴的情況下才會打破Bean生命周期的設計,如果一個Bean沒有出現(xiàn)循環(huán)依賴,那么它還是遵守了Bean的生命周期的設計的。
你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調(diào)度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
新聞名稱:Spring循環(huán)依賴源碼解析(深度理解)-創(chuàng)新互聯(lián)
網(wǎng)頁鏈接:http://vcdvsql.cn/article34/pjjse.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、關鍵詞優(yōu)化、服務器托管、軟件開發(fā)、網(wǎng)站設計公司、網(wǎng)站設計
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)