Spring——為什麼會有迴圈依賴(原始碼)

風中有朵雲做的雨發表於2019-04-18

前言

本文配合下面兩文食用味道更加

Spring啟動過程——原始碼分析1

Spring啟動過程——原始碼分析2

迴圈依賴是什麼?

google上都有這樣的答案

Spring——為什麼會有迴圈依賴(原始碼)
我看到都有個疑問 為什麼這樣就會有問題呢 我自己例項化沒有任何問題啊
但我突然又想到 我們的關注重點是 為什麼Spring有這樣的問題呢

為什麼Sprng會有迴圈依賴的問題呢?

思考
想知道為什麼spring為什麼有這樣的問題 前提是知道spring是怎麼例項化Bean的

Spring是怎麼例項化的呢

我們知道Spring例項化(無論單例還是多例)會先進入getBean 接著會進入doGetBean 它的原始碼重點大致如下(不瞭解的話 請去看下Spring啟動過程——原始碼分析2)

//先從singletonObjects拿
//拿不到 呼叫beforeSingletonCreation  this.singletonsCurrentlyInCreation.add(beanName)
         從this.singletonFactories.get(beanName).getObject()
getSingleton(beanName);
//經過上面的還是拿不到
//同樣先從singletonObjects拿 拿不到呼叫createBean 進入後會進到createBeanInstance
//createBeanInstance會根據例項化方式進行各自的例項化
sharedInstance = getSingleton(beanName, () -> createBean(beanName, mbd, args))
//建立完Bean放入快取singletonFactories.put(beanName, singletonFactory)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
!! 此時singletonObjects不存在上面的bean
//填充屬性
populateBean(beanName, mbd, instanceWrapper);
//初始化Bean 與這裡無關
exposedObject = initializeBean(beanName, exposedObject, mbd);
return exposedObject;
複製程式碼
//這個方法不是很清楚,但我debug發現基本都是直接return bean
getEarlyBeanReference(beanName, mbd, bean)
複製程式碼

重點getSingleton(beanName)

Spring——為什麼會有迴圈依賴(原始碼)
重點 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
指向原始物件的引用通過 ObjectFactory 暴露出去 將已經例項化的bean作為引數傳到方法裡面,迴圈依賴時等到再次執行getSingleton會執行processor直接返回這個Bean

Spring——為什麼會有迴圈依賴(原始碼)
Spring——為什麼會有迴圈依賴(原始碼)

Bean依賴時執行流程圖 StudentA和StudentB相互依賴的情況(從左到右Bean分別是StudentA、StudentB、StudentA)

Spring——為什麼會有迴圈依賴(原始碼)

Spring怎麼定義迴圈依賴

Spring容器會將每一個正在建立的Bean 識別符號放在一個“當前建立Bean池” Set集合singletonsCurrentlyInCreation中,Bean識別符號在建立過程中將一直保持 在這個池中,因此如果在建立Bean過程中發現自己已經在“當前建立Bean池”裡時將丟擲BeanCurrentlyInCreationException異常表示迴圈依賴

分析各種注入方式下,會不會出現迴圈依賴

三種注入方式
屬性注入
有參注入
setter注入

根據上邊的流程圖 那麼三種注入方式下,例項化的過程有什麼不一樣呢? 首先回顧上面的內容(實在太繞了 再看一下)

spring單例下真正例項化Bean的地方

        getSingleton(beanName, singletonFactory)
複製程式碼

大致執行如下

   Object singletonObject = this.singletonObjects.get(beanName);
   if(singletonObject!=null) return;
   //singletonsCurrentlyInCreation.add(name) 解決迴圈依賴
   beforeSingletonCreation(beanName);
   //真正例項化bean 其實也就是createBean這個方法 //最終會進入到無參或者有參的例項化
   singletonObject = singletonFactory.getObject();
   //singletonsCurrentlyInCreation.remove(name) 解決迴圈依賴
   afterSingletonCreation(beanName);
   addSingleton(beanName, singletonObject);//加入快取
   return singleObject
複製程式碼

spring多例下真正例項化Bean的地方

if (isPrototypeCurrentlyInCreation(beanName)) {
	throw new BeanCurrentlyInCreationException(beanName);
 }
 //this.prototypesCurrentlyInCreation.set(beanName)
beforePrototypeCreation(beanName);
//最終會進入到無參或者有參的例項化
prototypeInstance = createBean(beanName, mbd, args)
//this.prototypesCurrentlyInCreation.remove();
afterPrototypeCreation(beanName)
複製程式碼
    public BeanCurrentlyInCreationException(String beanName) {
	super(beanName,
	"Requested bean is currently in creation:  
	 Is there an unresolvable circular reference?");
    }
複製程式碼

以StudentA->StudentB->StudentA->為例

1. 單例情況下屬性注入和setter注入

首先StudentA呼叫getBean進入createBeanInstance這個方法裡,由於是無參例項化 會直接用反射返回一個物件bean,再呼叫 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))暴露ObjectFactory,接著進入populateBean,在這裡屬性還是setter注入的區別就是通過不同的方式去對StudentB賦值,一樣的是都要去getBean("StudentB")->getSingleton("StudentB")->getSingleton("StudentB",()->createBean))->createBeanInstance->addSingletonFactory->populateBean->getSingleton("StudentA") 執行到這裡 會直接return 剛剛addSingletonFactory時快取的Bean
此時singletonsCurrentlyInCreation也是有StudentA、B的 只不過沒有執行到getSingleton("StudentB",()->createBean)) 所以也就不會報異常

2. 單例情況下有參注入

進入createBeanInstance後
有參建構函式例項化會進入autowireConstructor方法 StudentA會先去獲取它引數裡的StudentB 此時呼叫getBean->getSingleton("StudentB",()->createBean))->beforeSingletonCreation("StudentB")->createBeanInstance 再次進入autowireConstructor
StudentB會先去獲取它引數裡的StudentA 此時呼叫getBean->getSingleton->beforeSingletonCreation("StudentA")
由於每次singletonsCurrentlyInCreation.add(beanName) 此時singletonsCurrentlyInCreation已經有StudentA
singletonsCurrentlyInCreation.add(beanName)==false -> 丟擲BeanCurrentlyInCreationException

3. 多例下屬性注入和setter注入

首先StudentA呼叫getBean進入doGetBean->isPrototypeCurrentlyInCreation("StudentA")->beforePrototypeCreation("StudentA")->createBean("StudentA", mbd, args)->createBeanInstance->populate->doGetBean("StudentB")->isPrototypeCurrentlyInCreation("StudentA")->beforePrototypeCreation("StudentB")->createBean("StudentB", mbd, args)->createBeanInstance->populate->doGetBean("StudentA")->isPrototypeCurrentlyInCreation("StudentA")此時返回true 直接拋異常

4. 多例 有參構造注入

首先StudentA呼叫getBean進入doGetBean 多例直接進入->isPrototypeCurrentlyInCreation("StudentA")->beforePrototypeCreation("StudentA")->createBean("StudentA", mbd, args)->createBeanInstance->autowireConstructor->doGetBean->isPrototypeCurrentlyInCreation("StudentB")->beforePrototypeCreation("StudentB")->createBean("StudentB", mbd, args)->createBeanInstance->autowireConstructor->doGetBean->**isPrototypeCurrentlyInCreation("StudentA")**此時返回true 直接拋異常

和上邊差不多

總結

Spring利用遞迴方式實現Bean的例項化過程,單例下利用singletonsCurrentlyInCreation判斷是否是迴圈依賴,多例下利用prototypesCurrentlyInCreation判斷, 開始建立bean會往集合add值,結束時則remove掉,這樣的規則,導致了多例下無法解決迴圈依賴,單例下無法解決構造引數注入的迴圈依賴
重點是理解整個建立過程

Spring——為什麼會有迴圈依賴(原始碼)

相關文章