1.單例Bean循環(huán)依賴-setter產(chǎn)生的循環(huán)依賴
1.1 原理
為單例搞的三個 map:
- 一級緩存,singletonObjects烂琴,存儲所有已創(chuàng)建完畢的單例 Bean (完整的 Bean)
- 二級緩存,earlySingletonObjects,存儲所有僅完成實例化蓝晒,但還未進行屬性注入和初始化的 Bean
- 三級緩存形用,singletonFactories,存儲能建立這個 Bean 的一個工廠,通過工廠能獲取這個 Bean妓雾,延遲化 Bean 的生成,工廠生成的 Bean 會塞入二級緩存
getBean()步驟:
- getSingleton(beanName) 依次從三個緩存中獲取
- 實例化
- addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
- 屬性注入
- 初始化
一個大致過程: getBean(A)肩狂,AB循環(huán)依賴
- A執(zhí)行屬性注入郊霎,這個時候 A 發(fā)現(xiàn)需要注入 B,所以去 getBean(B)移袍,此時又會走一遍上面描述的邏輯解藻。
- 到了 B 的屬性注入這一步,此時 B 調(diào)用 getBean(A)葡盗,這時候一級緩存里面找不到舆逃,但是發(fā)現(xiàn) A 正在創(chuàng)建中的,于是去二級緩存找,發(fā)現(xiàn)沒找到路狮,于是去三級緩存找虫啥,然后找到了。
并且通過上面提前在三級緩存里暴露的工廠得到 A奄妨,然后將這個工廠從三級緩存里刪除涂籽,并將 A 加入到二級緩存中。
然后結(jié)果就是 B 屬性注入成功砸抛。
緊接著 B 調(diào)用 initializeBean 初始化评雌,最終返回,此時 B 已經(jīng)被加到了一級緩存里 直焙。 - 這時候就回到了 A 的屬性注入景东,此時注入了 B,接著執(zhí)行初始化奔誓,最后 A 也會被加到一級緩存里斤吐,且從二級緩存中刪除 A。
1.2 getSingleton()
getSingleton()并發(fā)安全問題厨喂?以及5.3后變了和措,為啥變了?
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
// 到一級緩存獲取
Object singletonObject = this.singletonObjects.get(beanName);
// 條件一:一級緩存不存在
// 1)單實例未創(chuàng)建
// 2)單實例循環(huán)依賴(三級緩存解決setter循環(huán)依賴)
//1.假設Spring先實例化A蜕煌,首先拿到A的構(gòu)造方法派阱,進行反射創(chuàng)建出來A的早期實例對象,這個時候斜纪,這個早期對象被包裝成了ObjectFactory對象贫母,放到了3級緩存。
//2.處理A的依賴數(shù)據(jù)盒刚,檢查發(fā)現(xiàn)颁独,A它依賴了B對象,所以接下來伪冰,Spring就會去根據(jù)B類型到容器中去getBean(B.class)誓酒,這里就遞歸了。
//3.拿到B的構(gòu)造方法贮聂,進行反射創(chuàng)建出來B的早期實例對象靠柑,它也會把B包裝成ObjectFactory對象,放到3級緩存吓懈。
//4.處理B的依賴數(shù)據(jù)歼冰,檢查發(fā)現(xiàn),B它依賴了A對象耻警,所以接下來隔嫡,Spring就會去根據(jù)A類型到容器中去getBean(A.class)甸怕,去拿A對象,這個又遞歸了腮恩。
//5.程序還會走到當前這個方法梢杭。getSingleton這個方法。
//6.條件一成立秸滴,條件二也會成立武契。
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) {
//Spring為什么需要有3級緩存存在,而不是只有2級緩存呢荡含?
//AOP咒唆,靠什么實現(xiàn)的呢?動態(tài)代理
//靜態(tài)代理:需要手動寫代碼释液,實現(xiàn)一個新的java文件全释,這個java類 和 需要代理的對象 實現(xiàn)同一個接口,內(nèi)部維護一個被代理對象(原生)
//代理類误债,在調(diào)用原生對象前后浸船,可以加一些邏輯. 代理對象 和 被代理對象 是兩個不同的對象,內(nèi)存地址一定是不一樣的找前。
//動態(tài)代理:不需要人為寫代碼了,而是依靠字節(jié)碼框架動態(tài)生成class字節(jié)碼文件判族,然后jvm再加載躺盛,然后也一樣 也是去new代理對象,這個
//代理對象 沒啥特殊的形帮,也是內(nèi)部保留了 原生對象槽惫,然后在調(diào)用原生對象前后 實現(xiàn)的 字節(jié)碼增強。
//3級緩存在這里有什么目的呢辩撑?
//3級緩存里面保存的是對象工廠界斜,這個對象工廠內(nèi)部保留著最原生的對象引用,ObjectFactory的實現(xiàn)類合冀,getObject()方法各薇,它需要考慮一個問題。
//它到底要返回原生的君躺,還是增強后的峭判。
//getObject會判斷當前這個早期實例 是否需要被增強,如果是棕叫,那么提前完成動態(tài)代理增強林螃,返回代理對象。否則俺泣,返回原生對象疗认。
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 將三級緩存數(shù)據(jù)提升到二級
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
1.3 為啥需要三級緩存完残?
三級緩存是否為延遲代理的創(chuàng)建,盡量不打破 Bean 的生命周期
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;
}
如果有代理的話横漏,那么我們想要直接拿到的是代理對象谨设。
也就是說如果 A 需要被代理,那么 B 依賴的 A 是已經(jīng)被代理的 A绊茧,所以我們不能返回 A 給 B铝宵,而是返回代理的 A 給 B。
這個工廠的作用就是判斷這個對象是否需要代理华畏,如果否則直接返回鹏秋,如果是則返回代理對象。
正常代理對象的生成是基于后置處理器亡笑,是 在被代理的對象初始化后期調(diào)用生成的 侣夷, 所以如果你提早代理了其實是違背了 Bean 定義的生命周期 。
所以 Spring 先在一個三級緩存放置一個工廠仑乌,如果產(chǎn)生循環(huán)依賴百拓,那么就調(diào)用這個工廠提早得到代理對象。
如果沒產(chǎn)生依賴晰甚,這個工廠根本不會被調(diào)用衙传,所以 Bean 的生命周期就是對的。
其實破壞循環(huán)依賴厕九,其實只有二級緩存就夠了蓖捶,但是礙于生命周期的問題,提前暴露工廠延遲代理對象的生成扁远。
2.單例Bean循環(huán)依賴-構(gòu)造參數(shù)產(chǎn)生的循環(huán)依賴
2.1 構(gòu)造參數(shù)為啥不能解決循環(huán)依賴
如果全是構(gòu)造器注入俊鱼,比如 A(B b) ,那表明在 new 的時候畅买,就需要得到 B并闲,此時需要 new B 。
但是 B 也是要在構(gòu)造的時候注入 A 谷羞,即 B(A a) 帝火,這時候 B 需要在一個 map 中找到不完整的 A ,發(fā)現(xiàn)找不到湃缎。
為什么找不到购公?因為 A 還沒 new 完呢,所以找到不完整的 A雁歌, 因此如果全是構(gòu)造器注入的話宏浩,那么 Spring 無法處理循環(huán)依賴 。
總結(jié):
- 如果循環(huán)依賴都是構(gòu)造器注入靠瞎,則失敗
- 如果循環(huán)依賴不完全是構(gòu)造器注入比庄,則可能成功求妹,可能失敗,具體跟BeanName的字母序有關(guān)系佳窑。
2.2 源碼中的處理
getBean()步驟:
- getSingleton(beanName) 依次從三個緩存中獲取
- getSingleton(beanName, ObjectFactory)
- beforeSingletonCreation(beanName) 處理構(gòu)造器循環(huán)依賴
- 實例化制恍,這里會getBean()構(gòu)造器參數(shù)
- addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
- 屬性注入
- 初始化
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//容器銷毀時,會設置這個屬性為true神凑,這個時候就不能再創(chuàng)建bean實例了净神,直接拋錯。
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
//將當前beanName放入到“正在創(chuàng)建中單實例集合”溉委,放入成功鹃唯,說明沒有產(chǎn)生循環(huán)依賴,失敗瓣喊,則產(chǎn)生循環(huán)依賴坡慌,里面會拋異常。
//舉個例子:構(gòu)造方法參數(shù)依賴
//A->B B->A
//1.加載A藻三,根據(jù)A的構(gòu)造方法洪橘,想要去實例化A對象,但是發(fā)現(xiàn)A的構(gòu)造方法有一個參數(shù)是B(在這之前棵帽,已經(jīng)向這個集合中添加了 {A})
//2.因為A的構(gòu)造方法依賴B熄求,所以觸發(fā)了加載B的邏輯..
//3.加載B,根據(jù)B的構(gòu)造方法逗概,想要去實例化B對象弟晚,但是發(fā)現(xiàn)B的構(gòu)造方法有一個參數(shù)是A(在這之前,已經(jīng)向這個集合中添加了 {A仗谆,B})
//4.因為B的構(gòu)造方法依賴A指巡,所以再次觸發(fā)了加載A的邏輯..
//5.再次來到這個getSingleton方法里淑履,調(diào)用beforeSingletonCreation(A),因為創(chuàng)建中集合 已經(jīng)有A了隶垮,所以添加失敗,拋出異常
//完事秘噪。
beforeSingletonCreation(beanName);
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
3.原型模式的循環(huán)依賴
3.1 原型模式為啥不能解決循環(huán)依賴
如果兩個 Bean 都是原型模式的話狸吞。
那么創(chuàng)建 A1 需要創(chuàng)建一個 B1。
創(chuàng)建 B1 的時候要創(chuàng)建一個 A2指煎。
創(chuàng)建 A2 又要創(chuàng)建一個 B2蹋偏。
創(chuàng)建 B2 又要創(chuàng)建一個 A3。
創(chuàng)建 A3 又要創(chuàng)建一個 B3.....
因為原型模式都需要創(chuàng)建新的對象至壤,不能用以前的對象威始。
3.2 源碼中的處理
getBean()步驟:
- getSingleton(beanName) 依次從三個緩存中獲取
- 原型循環(huán)依賴問題判定
- beforePrototypeCreation(beanName); 加入到prototypesCurrentlyInCreation
- 實例化
- 屬性注入
- 初始化
- afterPrototypeCreation(beanName);
//1、原型循環(huán)依賴問題判定
//舉個例子:
//prototypeA -> B像街, B -> prototypeA
//1.會向正在創(chuàng)建中的原型集合內(nèi)添加一個字符串 “A” 下面beforePrototypeCreation(beanName);
//2.創(chuàng)建prototypeA對象黎棠,只是一個早期對象晋渺。
//3.處理prototypeA的依賴,發(fā)現(xiàn)A依賴了B類型的對象
//4.觸發(fā)了Spring.getBean(“B”)的操作脓斩。
//5.根據(jù)B的構(gòu)造方法反射創(chuàng)建出來了B的早期實例
//6.Spring處理B對象的依賴木西,發(fā)現(xiàn)依賴了A。
//7.Spring轉(zhuǎn)頭回來再次去獲取A去了随静。getBean(“A”).
//8.條件會返回true八千,最終拋出異常,算是結(jié)束了循環(huán)依賴注入燎猛。
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}