爛了大街的 Spring 循環(huán)依賴問(wèn)題,你以為自己就真會(huì)了嗎
new XXX。new XXX、序列化、clone() 等等, 只是 Spring ?是通過(guò)反射 + 工廠的方式創(chuàng)建對(duì)象并放在容器的,創(chuàng)建好的對(duì)象我們一般還會(huì)對(duì)對(duì)象屬性進(jìn)行賦值,才去使用,可以理解是分了兩個(gè)步驟。什么是循環(huán)依賴

public?class?BeanB?{
????private?BeanA?beanA;
????public?void?setBeanA(BeanA?beanA)?{
??this.beanA?=?beanA;
?}
}
public?class?BeanA?{
????private?BeanB?beanB;
????public?void?setBeanB(BeanB?beanB)?{
????????this.beanB?=?beanB;
?}
}
"beanA" ?class="priv.starfish.BeanA">
??"beanB"?ref="beanB"/> "beanB"?class="priv.starfish.BeanB">
??"beanA"?ref="beanA"/>

?
源碼解毒
代碼版本:5.0.16.RELEASE
?
獲取 Bean 流程

流程從 getBean方法開始,getBean是個(gè)空殼方法,所有邏輯直接到doGetBean方法中transformedBeanName將 name 轉(zhuǎn)換為真正的 beanName(name 可能是 FactoryBean 以 & 字符開頭或者有別名的情況,所以需要轉(zhuǎn)化下)然后通過(guò) getSingleton(beanName)方法嘗試從緩存中查找是不是有該實(shí)例 sharedInstance(單例在 Spring 的同一容器只會(huì)被創(chuàng)建一次,后續(xù)再獲取 bean,就直接從緩存獲取即可)如果有的話,sharedInstance 可能是完全實(shí)例化好的 bean,也可能是一個(gè)原始的 bean,所以再經(jīng) getObjectForBeanInstance處理即可返回當(dāng)然 sharedInstance 也可能是 null,這時(shí)候就會(huì)執(zhí)行創(chuàng)建 bean 的邏輯,將結(jié)果返回
/**?Cache?of?singleton?objects:?bean?name?-->?bean?instance?*/
private?final?Map?singletonObjects?=?new?ConcurrentHashMap<>(256);
/**?Cache?of?singleton?factories:?bean?name?-->?ObjectFactory?*/
private?final?Map>?singletonFactories?=?new?HashMap<>(16);
/**?Cache?of?early?singleton?objects:?bean?name?-->?bean?instance?*/
private?final?Map?earlySingletonObjects?=?new?HashMap<>(16);
singletonObjects:完成初始化的單例對(duì)象的 cache,這里的 bean 經(jīng)歷過(guò)實(shí)例化->屬性填充->初始化以及各種后置處理(一級(jí)緩存)earlySingletonObjects:存放原始的 bean 對(duì)象(完成實(shí)例化但是尚未填充屬性和初始化),僅僅能作為指針提前曝光,被其他 bean 所引用,用于解決循環(huán)依賴的 (二級(jí)緩存)singletonFactories:在 bean 實(shí)例化完之后,屬性填充以及初始化之前,如果允許提前曝光,Spring 會(huì)將實(shí)例化后的 bean 提前曝光,也就是把該 bean 轉(zhuǎn)換成beanFactory并加入到singletonFactories(三級(jí)緩存)
protected?Object?getSingleton(String?beanName,?boolean?allowEarlyReference)?{
????//?從?singletonObjects?獲取實(shí)例,singletonObjects?中的實(shí)例都是準(zhǔn)備好的?bean?實(shí)例,可以直接使用
????Object?singletonObject?=?this.singletonObjects.get(beanName);
????//isSingletonCurrentlyInCreation()?判斷當(dāng)前單例bean是否正在創(chuàng)建中
????if?(singletonObject?==?null?&&?isSingletonCurrentlyInCreation(beanName))?{
????????synchronized?(this.singletonObjects)?{
????????????//?一級(jí)緩存沒(méi)有,就去二級(jí)緩存找
????????????singletonObject?=?this.earlySingletonObjects.get(beanName);
????????????if?(singletonObject?==?null?&&?allowEarlyReference)?{
????????????????//?二級(jí)緩存也沒(méi)有,就去三級(jí)緩存找
????????????????ObjectFactory>?singletonFactory?=?this.singletonFactories.get(beanName);
????????????????if?(singletonFactory?!=?null)?{
????????????????????//?三級(jí)緩存有的話,就把他移動(dòng)到二級(jí)緩存,.getObject()?后續(xù)會(huì)講到
????????????????????singletonObject?=?singletonFactory.getObject();
????????????????????this.earlySingletonObjects.put(beanName,?singletonObject);
????????????????????this.singletonFactories.remove(beanName);
????????????????}
????????????}
????????}
????}
????return?singletonObject;
}

創(chuàng)建 bean 從以下代碼開始,一個(gè)匿名內(nèi)部類方法參數(shù)(總覺(jué)得 Lambda 的方式可讀性不如內(nèi)部類好理解) if?(mbd.isSingleton())?{
????sharedInstance?=?getSingleton(beanName,?()?->?{
????????try?{
????????????return?createBean(beanName,?mbd,?args);
????????}
????????catch?(BeansException?ex)?{
????????????destroySingleton(beanName);
????????????throw?ex;
????????}
????});
????bean?=?getObjectForBeanInstance(sharedInstance,?name,?beanName,?mbd);
}getSingleton()方法內(nèi)部主要有兩個(gè)方法public?Object?getSingleton(String?beanName,?ObjectFactory>?singletonFactory)?{
????//?創(chuàng)建?singletonObject
?singletonObject?=?singletonFactory.getObject();
????//?將?singletonObject?放入緩存
????addSingleton(beanName,?singletonObject);
}getObject()匿名內(nèi)部類的實(shí)現(xiàn)真正調(diào)用的又是createBean(beanName, mbd, args)往里走,主要的實(shí)現(xiàn)邏輯在 doCreateBean方法,先通過(guò)createBeanInstance創(chuàng)建一個(gè)原始 bean 對(duì)象接著 addSingletonFactory添加 bean 工廠對(duì)象到 singletonFactories 緩存(三級(jí)緩存)通過(guò) populateBean方法向原始 bean 對(duì)象中填充屬性,并解析依賴,假設(shè)這時(shí)候創(chuàng)建 A 之后填充屬性時(shí)發(fā)現(xiàn)依賴 B,然后創(chuàng)建依賴對(duì)象 B 的時(shí)候又發(fā)現(xiàn)依賴 A,還是同樣的流程,又去getBean(A),這個(gè)時(shí)候三級(jí)緩存已經(jīng)有了 beanA 的“半成品”,這時(shí)就可以把 A 對(duì)象的原始引用注入 B 對(duì)象(并將其移動(dòng)到二級(jí)緩存)來(lái)解決循環(huán)依賴問(wèn)題。這時(shí)候getObject()方法就算執(zhí)行結(jié)束了,返回完全實(shí)例化的 bean最后調(diào)用 addSingleton把完全實(shí)例化好的 bean 對(duì)象放入 singletonObjects 緩存(一級(jí)緩存)中,打完收工
?
Spring 解決循環(huán)依賴

Spring 創(chuàng)建 bean 主要分為兩個(gè)步驟,創(chuàng)建原始 bean 對(duì)象,接著去填充對(duì)象屬性和初始化 每次創(chuàng)建 bean 之前,我們都會(huì)從緩存中查下有沒(méi)有該 bean,因?yàn)槭菃卫?,只能有一個(gè) 當(dāng)我們創(chuàng)建 beanA 的原始對(duì)象后,并把它放到三級(jí)緩存中,接下來(lái)就該填充對(duì)象屬性了,這時(shí)候發(fā)現(xiàn)依賴了 beanB,接著就又去創(chuàng)建 beanB,同樣的流程,創(chuàng)建完 beanB 填充屬性時(shí)又發(fā)現(xiàn)它依賴了 beanA,又是同樣的流程,不同的是,這時(shí)候可以在三級(jí)緩存中查到剛放進(jìn)去的原始對(duì)象 beanA,所以不需要繼續(xù)創(chuàng)建,用它注入 beanB,完成 beanB 的創(chuàng)建 既然 beanB 創(chuàng)建好了,所以 beanA 就可以完成填充屬性的步驟了,接著執(zhí)行剩下的邏輯,閉環(huán)完成
ObjectFactory>?singletonFactory?=?this.singletonFactories.get(beanName);
if?(singletonFactory?!=?null)?{
????//?三級(jí)緩存有的話,就把他移動(dòng)到二級(jí)緩存
????singletonObject?=?singletonFactory.getObject();
????this.earlySingletonObjects.put(beanName,?singletonObject);
????this.singletonFactories.remove(beanName);
}
singletonFactory.getObject() 是一個(gè)接口方法,這里具體的實(shí)現(xiàn)方法在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;
????????????????//?這么一大段就這句話是核心,也就是當(dāng)bean要進(jìn)行提前曝光時(shí),
????????????????//?給一個(gè)機(jī)會(huì),通過(guò)重寫后置處理器的getEarlyBeanReference方法,來(lái)自定義操作bean
????????????????//?值得注意的是,如果提前曝光了,但是沒(méi)有被提前引用,則該后置處理器并不生效!!!
????????????????//?這也正式三級(jí)緩存存在的意義,否則二級(jí)緩存就可以解決循環(huán)依賴的問(wèn)題
????????????????exposedObject?=?ibp.getEarlyBeanReference(exposedObject,?beanName);
????????????}
????????}
????}
????return?exposedObject;
}
getEarlyBeanReference 是 SmartInstantiationAwareBeanPostProcessor 接口的默認(rèn)方法,真正實(shí)現(xiàn)這個(gè)方法的只有**AbstractAutoProxyCreator** 這個(gè)類,用于提前曝光的 AOP 代理。@Override
public?Object?getEarlyBeanReference(Object?bean,?String?beanName)?throws?BeansException?{
???Object?cacheKey?=?getCacheKey(bean.getClass(),?beanName);
???this.earlyProxyReferences.put(cacheKey,?bean);
???//?對(duì)bean進(jìn)行提前Spring?AOP代理
???return?wrapIfNecessary(bean,?beanName,?cacheKey);
}
@Service
public?class?HelloServiceImpl?implements?HelloService?{
???@Autowired
???private?HelloService?helloService;
???@Override
???@Transactional
???public?Object?hello()?{
??????return?"Hello?JavaKeeper";
???}
}
Service 類使用到了事務(wù),所以最終會(huì)生成一個(gè) JDK 動(dòng)態(tài)代理對(duì)象 Proxy。剛好它又存在自己引用自己的循環(huán)依賴,完美符合我們的場(chǎng)景需求。@Component
public?class?HelloProcessor?implements?SmartInstantiationAwareBeanPostProcessor?{
?@Override
?public?Object?getEarlyBeanReference(Object?bean,?String?beanName)?throws?BeansException?{
??System.out.println("提前曝光了:"+beanName);
??return?bean;
?}
}
HelloProcessor,說(shuō)明這個(gè) bean 會(huì)通過(guò) AOP 代理處理。
protected?Object?doCreateBean(?...?){
?...
?
?boolean?earlySingletonExposure?=?(mbd.isSingleton()?&&?this.allowCircularReferences?&&?isSingletonCurrentlyInCreation(beanName));
????//?需要提前暴露(支持循環(huán)依賴),就注冊(cè)一個(gè)ObjectFactory到三級(jí)緩存
?if?(earlySingletonExposure)?{?
????????//?添加?bean?工廠對(duì)象到?singletonFactories?緩存中,并獲取原始對(duì)象的早期引用
??//匿名內(nèi)部方法?getEarlyBeanReference?就是后置處理器?
??//?SmartInstantiationAwareBeanPostProcessor?的一個(gè)方法,
??//?它的功效為:保證自己被循環(huán)依賴的時(shí)候,即使被別的Bean @Autowire進(jìn)去的也是代理對(duì)象
??addSingletonFactory(beanName,?()?->?getEarlyBeanReference(beanName,?mbd,?bean));
?}
?//?此處注意:如果此處自己被循環(huán)依賴了??那它會(huì)走上面的getEarlyBeanReference,從而創(chuàng)建一個(gè)代理對(duì)象從??三級(jí)緩存轉(zhuǎn)移到二級(jí)緩存里
?//?注意此時(shí)候?qū)ο筮€在二級(jí)緩存里,并沒(méi)有在一級(jí)緩存。并且此時(shí)后續(xù)的這兩步操作還是用的 exposedObject,它仍舊是原始對(duì)象~~~
?populateBean(beanName,?mbd,?instanceWrapper);
?exposedObject?=?initializeBean(beanName,?exposedObject,?mbd);
?//?因?yàn)槭聞?wù)的AOP自動(dòng)代理創(chuàng)建器在getEarlyBeanReference?創(chuàng)建代理后,initializeBean?就不會(huì)再重復(fù)創(chuàng)建了,二選一的)
?????
?//?所以經(jīng)過(guò)這兩大步后,exposedObject?還是原始對(duì)象,通過(guò)?getEarlyBeanReference?創(chuàng)建的代理對(duì)象還在三級(jí)緩存呢
?
?...
?
?//?循環(huán)依賴校驗(yàn)
?if?(earlySingletonExposure)?{
????????//?注意此處第二個(gè)參數(shù)傳的false,表示不去三級(jí)緩存里再去調(diào)用一次getObject()方法了~~~,此時(shí)代理對(duì)象還在二級(jí)緩存,所以這里拿出來(lái)的就是個(gè)?代理對(duì)象
??//?最后賦值給exposedObject??然后return出去,進(jìn)而最終被addSingleton()添加進(jìn)一級(jí)緩存里面去??
??//?這樣就保證了我們?nèi)萜骼?最終實(shí)際上是代理對(duì)象,而非原始對(duì)象~~~~~
??Object?earlySingletonReference?=?getSingleton(beanName,?false);
??if?(earlySingletonReference?!=?null)?{
???if?(exposedObject?==?bean)?{?
????exposedObject?=?earlySingletonReference;
???}
??}
??...
?}
?
}
自我解惑:
問(wèn):還是不太懂,為什么這么設(shè)計(jì)呢,即使有代理,在二級(jí)緩存代理也可以吧 | 為什么要使用三級(jí)緩存呢?
AOP 跟 Bean 的生命周期的設(shè)計(jì)。getObject() 方法并非直接返回實(shí)例,而是對(duì)實(shí)例又使用 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference 方法對(duì) bean 進(jìn)行處理,也就是說(shuō),當(dāng) Spring 中存在該后置處理器,所有的單例 bean 在實(shí)例化后都會(huì)被進(jìn)行提前曝光到三級(jí)緩存中,但是并不是所有的 bean 都存在循環(huán)依賴,也就是三級(jí)緩存到二級(jí)緩存的步驟不一定都會(huì)被執(zhí)行,有可能曝光后直接創(chuàng)建完成,沒(méi)被提前引用過(guò),就直接被加入到一級(jí)緩存中。因此可以確保只有提前曝光且被引用的 bean 才會(huì)進(jìn)行該后置處理。protected?Object?getSingleton(String?beanName,?boolean?allowEarlyReference)?{
????Object?singletonObject?=?this.singletonObjects.get(beanName);
????if?(singletonObject?==?null?&&?isSingletonCurrentlyInCreation(beanName))?{
????????synchronized?(this.singletonObjects)?{
????????????singletonObject?=?this.earlySingletonObjects.get(beanName);
????????????if?(singletonObject?==?null?&&?allowEarlyReference)?{
?????????????//?三級(jí)緩存獲取,key=beanName?value=objectFactory,objectFactory中存儲(chǔ)?????//getObject()方法用于獲取提前曝光的實(shí)例
????????????????ObjectFactory>?singletonFactory?=?this.singletonFactories.get(beanName);
????????????????if?(singletonFactory?!=?null)?{
????????????????????//?三級(jí)緩存有的話,就把他移動(dòng)到二級(jí)緩存
????????????????????singletonObject?=?singletonFactory.getObject();
????????????????????this.earlySingletonObjects.put(beanName,?singletonObject);
????????????????????this.singletonFactories.remove(beanName);
????????????????}
????????????}
????????}
????}
????return?singletonObject;
}
boolean?earlySingletonExposure?=?(mbd.isSingleton()?&&?this.allowCircularReferences?&&
??????isSingletonCurrentlyInCreation(beanName));
if?(earlySingletonExposure)?{
???if?(logger.isDebugEnabled())?{
??????logger.debug("Eagerly?caching?bean?'"?+?beanName?+
????????????"'?to?allow?for?resolving?potential?circular?references");
???}
???//?添加?bean?工廠對(duì)象到?singletonFactories?緩存中,并獲取原始對(duì)象的早期引用
???//匿名內(nèi)部方法?getEarlyBeanReference?就是后置處理器
???//?SmartInstantiationAwareBeanPostProcessor?的一個(gè)方法,
???//?它的功效為:保證自己被循環(huán)依賴的時(shí)候,即使被別的Bean @Autowire進(jìn)去的也是代理對(duì)象~~~~? AOP自動(dòng)代理創(chuàng)建器此方法里會(huì)創(chuàng)建的代理對(duì)象~~~
???addSingletonFactory(beanName,?()?->?getEarlyBeanReference(beanName,?mbd,?bean));
}
再問(wèn):AOP 代理對(duì)象提前放入了三級(jí)緩存,沒(méi)有經(jīng)過(guò)屬性填充和初始化,這個(gè)代理又是如何保證依賴屬性的注入的呢?
cglib代理還是jdk動(dòng)態(tài)代理生成的代理類,代理時(shí),會(huì)將目標(biāo)對(duì)象 target 保存在最后生成的代理 $proxy 中,當(dāng)調(diào)用 $proxy 方法時(shí)會(huì)回調(diào) h.invoke,而 h.invoke 又會(huì)回調(diào)目標(biāo)對(duì)象 target 的原始方法。所有,其實(shí)在 AOP 動(dòng)態(tài)代理時(shí),原始 bean 已經(jīng)被保存在 提前曝光代理中了,之后 原始 bean 繼續(xù)完成屬性填充和初始化操作。因?yàn)?AOP 代理$proxy中保存著 traget 也就是是 原始bean 的引用,因此后續(xù) 原始bean 的完善,也就相當(dāng)于Spring AOP中的 target 的完善,這樣就保證了 AOP 的屬性填充與初始化了!?
非單例循環(huán)依賴
<bean?id="beanA"?class="priv.starfish.BeanA"?scope="prototype">
???<property?name="beanB"?ref="beanB"/>
bean>
<bean?id="beanB"?class="priv.starfish.BeanB"?scope="prototype">
???<property?name="beanA"?ref="beanA"/>
bean>
Error?creating?bean?with?name?'beanA'?defined?in?class?path?resource?[applicationContext.xml]:?Cannot?resolve?reference?to?bean?'beanB'?while?setting?bean?property?'beanB';
Error?creating?bean?with?name?'beanB'?defined?in?class?path?resource?[applicationContext.xml]:?Cannot?resolve?reference?to?bean?'beanA'?while?setting?bean?property?'beanA';
Caused?by:?org.springframework.beans.factory.BeanCurrentlyInCreationException:?Error?creating?bean?with?name?'beanA':?Requested?bean?is?currently?in?creation:?Is?there?an?unresolvable?circular?reference?
prototype 作用域的 bean,Spring 容器無(wú)法完成依賴注入,因?yàn)?Spring 容器不進(jìn)行緩存 prototype 作用域的 bean ,因此無(wú)法提前暴露一個(gè)創(chuàng)建中的bean 。if?(isPrototypeCurrentlyInCreation(beanName))?{
???throw?new?BeanCurrentlyInCreationException(beanName);
}
?
構(gòu)造器循環(huán)依賴
public?class?BeanA?{
???private?BeanB?beanB;
???public?BeanA(BeanB?beanB)?{
??????this.beanB?=?beanB;
???}
}
public?class?BeanB?{
?private?BeanA?beanA;
?public?BeanB(BeanA?beanA)?{
??this.beanA?=?beanA;
?}
}
<bean?id="beanA"?class="priv.starfish.BeanA">
<constructor-arg?ref="beanB"/>
bean>
<bean?id="beanB"?class="priv.starfish.BeanB">
<constructor-arg?ref="beanA"/>
bean>

Circular dependencies
If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.
For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a
BeanCurrentlyInCreationException.One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.
Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).
?
小總結(jié) | 面試這么答
B 中提前注入了一個(gè)沒(méi)有經(jīng)過(guò)初始化的 A 類型對(duì)象不會(huì)有問(wèn)題嗎?
Spring 是如何解決的循環(huán)依賴?
singletonObjects),二級(jí)緩存為提前曝光對(duì)象(earlySingletonObjects),三級(jí)緩存為提前曝光對(duì)象工廠(singletonFactories)。為什么要使用三級(jí)緩存呢?二級(jí)緩存能解決循環(huán)依賴嗎?
AnnotationAwareAspectJAutoProxyCreator 這個(gè)后置處理器來(lái)在 Bean 生命周期的最后一步來(lái)完成 AOP 代理,而不是在實(shí)例化后就立馬進(jìn)行 AOP 代理。?
參考與感謝:
《Spring 源碼深度解析》- 郝佳著
https://developer.aliyun.com/article/766880
http://www.tianxiaobo.com/2018/06/08/Spring-IOC-容器源碼分析-循環(huán)依賴的解決辦法
https://cloud.tencent.com/developer/article/1497692
https://blog.csdn.net/chaitoudaren/article/details/105060882
有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
