循環依賴在spring框架中有一個專有名詞叫 Circular dependencies,其具體是指受spring管理的兩個bean對象 Bean1和Bean2,Bean1中有成員變量Bean2;Bean2中有成員變量Bean1。具體代碼case以下:html
代碼結構如圖:java
前先後後一共使用了四個類,其中兩個Bean類以下:算法
@Component
public class Chicken {
@Autowired
Egg egg;
}
複製代碼
@Component
public class Egg {
@Autowired
Chicken chicken;
}
複製代碼
一個配置類:spring
@ComponentScan("spring.post1.beans")
public class Config {
}
複製代碼
一個簡單的main方法啓動類:編程
public class DemoSpringCircularDependencies {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
Chicken chicken = ac.getBean("chicken", Chicken.class);
System.out.println(chicken);
}
}
複製代碼
經過代碼能夠看出,本章主要討論下spring怎麼解決基於@AutoWired註解的Bean的循環依賴問題。而兩個循環依賴的Bean就是Chiken(裏面須要屬性Egg)和Egg(裏面須要屬性Chicken)。緩存
學習本文前須要對spring的基於註解的bean管理配置方式有基本的瞭解,否則看不懂上述4個類的做用,那麼就無從談及學習spring源碼了,本系列的文章也不是基本的spring配置學習文章,這部分知識自行google。bash
須要對jdk8的lambda有基礎的瞭解。數據結構
首先咱們先看下須要分析的源碼的主要棧幀:app
先對上圖作簡單的說明,上圖中的每藍色小塊表明一個方法,裏面的數字部分表示方法的執行前後順序(數字小的先執行)。兩個相鄰的方法之間大數字方法是程序在執行小數字方法的過程當中要調用的方法(和debug時的的棧信息相似)。咱們對源碼的分析也將按照「建立全部單例Bean」,「建立Chicken對象」,「填充Chicken對象屬性」,「建立Egg對象」,「填充Egg對象屬性」,「獲取Chicken對象」等順序進行。框架
方法1. 是AnnotationConfigApplicationContext類的構造方法,構造方法引出對Bean的初始化建立操做。其中能夠留意下方法2. 中要執行的finishBeanFactoryInitialization方法也就是源碼棧幀圖中的3.方法。在方法3.上面有一句英文註釋: 「 // Instantiate all remaining (non-lazy-init) singletons. 」,清晰的代表方法3.的主要目的就是要建立剩下沒被建立的非懶加載的單例對象。那麼咱們定義的兩個Bean對象Chiken和Egg顯然是在這個方法裏面建立的,至於爲何是「剩下的」而不是全部的,其它的非懶加載的單例對象是在哪裏建立的,不是本文要描述的問題。
spring建立在建立Bean對象前會給每一個Bean對象建立一個BeanDefinition對象,BeanDefinition對象會蒐集用戶定義的關於Bean的各類配置信息,如這個Bean對象的類型,這個Bean對象的id和name,是否爲單例對象等等,這些配置信息能夠是xml形式的配置文件,也能夠是基於註解的配置信息。
以BeanDefinition的形式蒐集了這些信息後,spring就開始初始化非懶加載的單例對象了(這裏咱們只分析咱們本身定義的和循環依賴相關的兩個Bean對象Chicken和Egg的加載過程)。也就是執行 5. getBean方法。方法5. 是個空殼方法其內部調用的是方法 6. doGetBean方法。doGetBean方法執行過程當中會執行一個名爲getSingleton(String beanName, boolean allowEarlyReference) 的方法。此方法定義以下:
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) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
複製代碼
其主要流程就是經過beanName參數查看先查看map對象singletonObjects 中是否有對應名稱的Bean對象,有返回此Bean對象;沒有查看map對象 earlySingletonObjects 中是否有對應名稱的Bean對象,有返回此Bean對象;沒有查看map對象 singletonFactories 是否有對應名稱的ObjectFactory對象,有經過ObjectFactory對象的getObject方法獲取到對應的Bean對象,而後清除 singletonFactories 對應的beanName的映射,同時將獲得的Bean對象放到 earlySingletonObjects 中。這其中還有一個方法isSingletonCurrentlyInCreation(String beanName) 其內部是經過查看一個名爲singletonsCurrentlyInCreation的Set對象是否包含指定的beanName,來判斷這個單例bean是否正在建立bean對象。
這三個Map對象和一個Set對象就是Spring中解決循環依賴很是重要的緩存,一下咱們簡稱 「三Map一Set」,三個map對象因其在執行獲取beanName對應的Bean對象的過程當中的前後執行順序,分別簡稱爲 一級緩存、二級緩存、三級緩存。
singletonObjects:一級緩存。此緩存中的Bean對象是經歷Spring完整生命週期的Bean對象,
earlySingletonObjects:二級緩存。此緩存中的Bean對象是已經經過建立出來的但沒有經歷spring完整的生命週期的Bean對象。
singletonFactories:三級緩存。此緩存存在的是beanName和能獲取Bean對象的一個工廠類ObjectFactory對象。
方法6. doGetBean 第一次調用 getSingleton(String beanName) 方法時從三個緩存中都沒能得到參數chicken對應的Bean對象,程序繼續執行到方法7. getSingleton(String beanName, ObjectFactory<?> singletonFactory) ,7. 方法中會執行一個 名爲 beforeSingletonCreation(String beanName) 的方法,這個方法會在咱們上文提到的中的三Map一Set中的Set添加對應的beanName(chicken)表示此chicken對應的單例Bean處在正在建立過程當中,程序繼續執行執行到 8. getObject() 方法,方法8. 是一個lambada對象對應的方法,其調用的是方法9. createBean方法進入Bean的建立過程。方法9. 中咱們重點關注其執行的方法10. doCreateBean ,此方法是真正執行bean對象的建立的方法。在方法10. 中咱們注意到其會執行一個名爲 addSingletonFactory 方法,此方法會在咱們提到的 三Map一Set中的 三級緩存singletonFactories添加一個beanName(chicken)對應的ObjectFactory對象,然後執行方法10. 中的方法11. populateBean,此時程序傳給方法11. 的三個參數分別爲beanName:值爲chicken、mbd:ChickenBean的BeanDefiniton對象、instanceWrapper:經過構造方法建立的一個Chicken對象,即Spring註解中常常提到的raw bean對象。由方法11.的名稱可知,此類的主要目的是填充Chicken對象中的屬性(Egg對象),循環依賴正是在此方法中解決的。
緊接上面小節,方法11.中spring經過在Chicken類中的@Autowired 註解來發現其須要的屬性:Egg對象並填充其值,這個過程在方法12. postProcessProperties方法中執行,順便提一句@Autowired註解依賴的屬性由AutowiredAnnotationBeanPostProcessor類處理,@Resource註解依賴的屬性由CommonAnnotationBeanPostProcessor 類處理。 而方法13. 到方法17. 主要做用就是找到合適的beanName以便用來經過此beanName找到對應的Bean來填充Chicken中的Egg對象,此部分代碼和本文主旨無關之後的文章會分析,感興趣的童鞋能夠自行debug看下代碼。
緊接上面小節,spring經過方法17. resolveCandidate將找到的合適beanName(egg)傳遞下來,經過方法18. getBean 來執行對Egg Bean對象的獲取操做。此小節調用的方法棧和 「3.3 建立chicken對象」 小節的方法棧是同樣的,惟一的區別是3.3小節傳遞的beanName參數值爲chicken,而本小節傳遞的beanName參數爲egg。
本小節對標的是 「3.4 填充Chicken對象屬性」小節,兩個小節調用的方法棧是同樣的,區別也是參數的不一樣而已。Spring發現Egg對象須要注入一個Chicken對象。
這裏咱們分析的方法31. getBean 和方法18. getBean都是由於咱們本身定義的Bean對象中有須要的注入的Bean對象。可是方法31. 傳遞的參數是chicken,而Chicken對象在 3.3小節中分析得知,其在三Map一Set中的第三級緩存singletonFactories存放了一個對應的ObjectFactory對象。spring經過這個ObjectFactory對象獲取到了對應的Chicken 對象,而避免了循環依賴。
經過3.6小節咱們獲取到了Egg對象須要的成員變量Chicken對象。隨着方法棧幀的層層返回,咱們將焦點聚焦在由方法21.返回後的方法20.中,在程序執行完方法21. getObject 並獲取到經歷完Bean生命週期的Egg Bean後,其在方法20. 中還要執行兩個比較重要的方法 afterSingletonCreation 和 addSingleton,其中前者會把三Map一Set中的Set對象singletonsCurrentlyInCreation中的egg移除,表示此Bean對象不是正在建立的Bean對象,Bean建立已經完成;後者會把Egg Bean存放在一級緩存中,同時清空二級緩存和三級緩存中egg對應的映射,至此Egg Bean的spring生命週期已經大致完成。Chicken對象也會執行afterSingletonCreation 和 addSingleton 兩個方法來完成Chicken Bean的spring生命週期。
建立chicken對象、建立Egg對象:步驟主要解決一個Bean的raw bean對象的建立和的前期準備工做,和本文循環依賴相關的主要是對三Map一Set的對象的保存的內容的修改。
填充Chicken對象屬性、填充Chicken對象屬性:本文中主要經過AutowiredAnnotationBeanPostProcessor類完成依賴對象的蒐集和適合依賴對象的beanName的篩選。
獲取Chicken對象:主要是經過第三級緩存來獲取,避免了Chicken對象的重複建立而進入一個死循環。
緩存建立完的Egg 和緩存建立完的Chicken:完成善後工做,將走完spring生命週期的Egg Bean和Chicken Bean放到一級緩存中,供客戶端程序從spring中獲取使用。
在 「3 源碼分析」 章節中,隨着程序運行過程當中除了有由方法調用和方法返回而產生的線程方法棧圖中方法的壓棧和出棧外。在這進進出出的背後發生改變的是咱們的三Map一Set 中的數據。
在源碼分析的開頭小節「3.3 建立Chicken對象」 和結尾小節「3.8 緩存建立完的Egg 和緩存建立完的Chicken」咱們有對三Map一Set的分析,但着並非說只有這兩個小節的部分有數據變動,而是其緩存變化的原理和這連個小節一直,惟一的區別是方法調用的參數不一樣。整個數據變化圖以下:
圖中每一個狀態圖都有一個「[a,b)」形式的步驟指示器,其中a,b分別表示 「3.1小節」 中源碼棧幀圖中一個方法數字,而括號用的是高等數學中常見的方式左閉右開方式,表示在程序在執行方法a到方法b(包含a不包含b)過程當中緩存數據的狀態和其下面的表格一致。
經過對八個表格數據的觀察咱們能夠發現,對於同一個beanName所映射的對象,基本上經歷從第三級緩存、第二級緩存、第一級緩存,的一個升級過程。而對網上常常困惑的第三級緩存的做用(認爲第三級緩存沒有必要存在),博主認爲存在第三級緩存是基於如下兩個事實的:
某些Bean對象(並非全部的bean對象)在建立過程當中且還沒有建立完時就會被其它Bean對象所引用的問題(就是循環依賴,貌似是一句廢話^_^)。
Bean的生命週期過程是一個成本較高的過程。
本文中只有Chicken 對象在建立過程當中有被其它對象引用而Egg對象沒有。由於第三級緩存存儲的是一個raw bean後續建立的方法,那麼對於在建立時被其它對象引用的Chicken對象來講,能夠執行完第三級緩存中存儲的bean對象後續的處理方法(AOP的功能就是在此實現的)後將Chicken bean返回,對於沒有在建立過程當中被引用的Egg對象來講,其只是浪費第三級緩存中的一點點內存,而避免重複執行spring對Egg Bean的某些生命週期邏輯的重複執行,這些重複的邏輯極可能是很高成本的過程,如AOP的實現。
編程界有個很著名的說法:「算法加數據結構等於程序」,本文的「3 源碼分析」和「4 緩存數據變化」分別充當了spring解決基於@AutoWired註解的Bean的循環依賴程序中的算法和數據結構。和理解其關鍵是對「三Map一Set」數據變化的深刻理解。