天天看點

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

getBean流程源碼解析

公衆号:完美的工程學

gitte位址:https://gitee.com/duchenxi/total-war

工欲善其事,必先利其器!

getBean方法是spring ioc的核心,閱讀getBean方法的源碼也是了解spring容器工作原理所必須要做的事情!

我們先來看一下getBean方法,getBean的具體實作邏輯在AbstractBeanFactory類裡面的doGetBean方法中。

首先簡略地介紹一下整體的執行流程:

  • 1.根據傳入beanName擷取bean的别名
  • 2.嘗試從緩存中擷取之前被執行個體化過了的單例bean
  • 3.根據上面擷取到的執行個體進一步擷取bean(因為擷取到的可能是一個工廠bean)
  • 4.如果上面的步驟之後沒有擷取到bean那麼就需要建立bean了
  • 5.先根據緩存判斷一下目前的bean是否正在被建立,如果是的話表示依賴循環了
  • 6.嘗試擷取目前工廠的父工廠并從目前工廠的bean定義緩存中擷取bean定義委托父工廠去生成
  • 7.如果目前要擷取的bean隻是為了進行類型檢查就标記bean已經被建立
  • 8.同目前bean的父類合并bean的定義,并檢查擷取到的bean定義是不是抽象的
  • 9.通過上面擷取到的bean定義找到目前bean的依賴bean并遞歸調用getBean方法擷取依賴bean
  • 10.判斷bean的scope是單例的還是原型的或者是其他的建立bean
  • 11.根據要求傳回的bean類型通過convertService來對bean進行轉換
【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

上面是整體流程的簡述,接下來我們細細地研究一下每個步驟的執行過程。

1. beanName轉換

在doGetBean方法邏輯開始的時候就對傳入的beanName進行了處理,調用了transformedBeanName方法,這個方法的主要處理邏輯是去掉目前beanName的工廠bean字首,也就是"&",去掉了工廠名字首之後就去aliasMap也就是别名和bean規範名map中去擷取目前bean的規範名。關于工廠bean字首比如使用者想擷取一個叫teaFactory的FactoryBean的執行個體,那麼在getBean方法的參數中就需要傳入&teaFactory,之後容器就會執行個體化這個工廠bean給調用者,而不是傳回這個工廠bean所産生的bean對象。

2. 三級緩存中擷取單例

這個方法算是整個流程中比較重要的方法了,先嘗試從三級緩存中擷取bean的單例對象,所謂三級緩存就是三個等級的map。一級緩存是singletonObjects這個存放的就是初始化可以直接使用的bean對象,二級緩存earlySingletonObjects是早期對象是還在初始化過程中對象已經建立了但是還需要進一步處理的bean,三級緩存是singletonFactories這裡存放的是擷取目前bean的工廠。

具體的流程是先嘗試從一級緩存中擷取bean對象如果擷取到了就直接傳回,如果沒有擷取到bean對象的話就判斷目前的bean對象是否正在被建立,這個過程是通過查詢singletonsCurrentlyInCreation這個緩存map中是否有目前要擷取的bean,如果有的話表示目前的bean正在被建立也就是說在二級或者三級緩存中可能會擷取到bean。之後如果目前bean允許早期依賴的話就會進一步去二三級緩存中去擷取bean,在接下來的過程中會先對一級緩存加鎖,防止别的線程對一級緩存進行了修改。之後會去二級緩存中嘗試擷取早期對象,如果沒有擷取到的話就通過三級緩存存放的ObjectFactory去擷取bean對象,如果通過ObjectFactory擷取到了bean的話就将這個bean放到二級緩存中,并清除三級緩存中的這個ObjectFactory對象。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

3. 根據bean的執行個體擷取bean對象

将bean在緩存中擷取到了之後還要通過調用getObjectForBeanInstance方法來對擷取的bean進一步進行處理。這個方法的幹的事情很簡單,從方法上面的注釋就可以知道如果這個bean就是一個普通的bean那麼很簡單就隻是傳回bean本身,如果目前bean是一個工廠bean如果調用者就是想要一個工廠bean那麼就直接把這個工廠bean傳回給調用者,如果不是就通過這個工廠bean生産一個bean給調用者。

具體的處理邏輯是先判斷傳入的beanName是不是有工廠bean的字首也就是"&"。如果是的話就表示調用者就是想擷取工廠bean,之後在判斷這個bean的類型是不是FactoryBean類型如果不是的話就抛出異常,是的話就傳回這個bean。

如果根據beanName判斷使用者不是想擷取工廠bean的話就判斷一下bean的類型是不是不為工廠bean,如果是的話就傳回這個普通的bean。

如果目前的beanName是一個普通的bean但是bean的類型卻是一個工廠bean的類型,那麼就通過這個工廠bean去生産bean再傳回給調用者。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

下面我們來看一下spring是怎麼通過工廠bean來擷取bean的。我們點開getObjectFromFactoryBean方法,這個方法的處理邏輯也很清晰,處理的方式分為兩種,一種是單例一種不是單例,對于單例的bean的建立首先要鎖住單例池防止别的線程也在建立這個bean而産生了多個bean的執行個體。之後會在factoryBeanObjectCache這個緩存中檢查目前的這個工廠bean有沒有在之前被調用過而産生過bean,這裡也是為了防止單例的多執行個體。如果沒有的話就會調用doGetObjectFromFactoryBean方法通過我們傳入的工廠bean來擷取一個bean的執行個體,這個方法我們接下來會說到。在使用工廠bean建立完了bean對象之後會再去檢查一遍doGetObjectFromFactoryBean緩存看看有沒有其他線程在我們目前線程建立bean的時候也通過工廠建立了bean,如果建立了的話就直接使用那個線程建立的bean,這也是防止單例的多執行個體。完成上面的步驟之後我們的bean對象還不算是完全符合我們的預期的,這時候我們需要判斷一下目前的bean是否需要被後置處理,如果需要的話會調用bean的後置處理器來對bean進行處理,而需要注意的是在對bean調用後置處理器之前以及之後會分别調用兩個方法,将目前bean正在被建立的狀态放到緩存中去,防止别的線程也同時對這個bean進行了後置處理。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

點開beforeSingletonCreation方法我們可以看到這個保證隻有一個線程調用bean的後置處理器的處理方式很簡單,就是先判斷一下inCreationCheckExclusions這個緩存中有沒有目前的beanName同時判斷并将beanName添加到singletonsCurrentlyInCreation緩存中,之後在bean後置處理器調用完成之後會在singletonsCurrentlyInCreation緩存中移除beanName,這樣就保證了目前的bean隻會被一個線程去處理而且隻會被處理一次。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

而調用後置處理器的邏輯就很簡單了,隻是從beanPostProcessors緩存中擷取我們之前容器中注冊到容器的bean後置處理器再依次執行就完成了,具體的後置處理器的實作邏輯就看具體的後置處理器的邏輯了,我們也可以通過實作BeanPostProcessor接口,實作postProcessBeforeInitialization和postProcessAfterInitialization兩個方法來定制化地實作自己的對bean的特殊處理邏輯,關于bean的後置處理器我之後會詳細的講解。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

我們回到getObjectFromFactoryBean方法,在完成了對bean的後置處理之後bean就算被建立完成了,這時候我們需要将這個bean放到單例緩存中,老規矩為了防止單例bean的多執行個體這裡會先判斷一下單例緩存中是否有别的線程将bean先我們一步放到單例緩存中了,如果沒有的話就将目前的bean放到緩存中。

以上是針對于單例bean的處理方式,而對于非單例的bean的處理方式就簡單粗暴多了,邏輯更加簡單直接調用我們doGetObjectFromFactoryBean方法去擷取一個bean對象,之後再調用後置處理器。

以上就是通過spring通過工廠bean來擷取bean對象的整體流程了,在這裡我們介紹一下工廠bean,工廠bean也就是FactoryBean,通過FactoryBean類提供的工廠方法getObject我們可以擷取訂制化的bean對象。有興趣的同學可以點開FactoryBean這個類看一下裡面就定義了三個方法getObjectType:擷取生成的bean的類型;getObject擷取bean對象;isSingleton:是否是單例,預設傳回的是true。至于getObject的邏輯要看具體的子類的實作邏輯了。

關于doGetObjectFromFactoryBean方法處理邏輯也很簡單就是調用一個factoryBean的getObject方法去擷取一個bean,如果傳回的是一個null的話就建立一個NullBean類型的bean傳回,邏輯很簡單這裡就不展開說了。

4. 判斷原型bean的依賴循環

以上的流程就是關于單例bean已經或者正在被建立或者是單例的工廠被已經或正被建立對其的處理工作,如果目前的bean第一次被建立呢?或者目前的bean不是一個單例的bean的呢?是以接下來的代碼是針對于目前bean的第一次建立。接下來的這個步驟就是對原型bean的依賴循環做判斷。

通過這段代碼上的注釋我們可以知道這部分的代碼是判斷原型模式的bean是否正在被建立的,如果正在被建立那麼我們很可能就陷入原型模式的依賴循環了,這裡會直接抛出異常。針對于原型模式的任何類型的循環依賴spring都會直接抛出異常。因為原型模式的循環依賴由于bean不能保證單例那麼假如是這樣的依賴循環A->B->C->A,如果A是原型bean的話那麼兩個A的對象根本就不是同一個對象就談不上依賴了。而針對與單例模式的bean,spring是可以處理setter注入的bean的但是處理不了構造器注入的bean,這個會在後面的部分詳細說明。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

關于這裡是怎麼判斷目前的原型bean正在被建立的,其實處理邏輯很簡單,點開isPrototypeCurrentlyInCreation方法我們可以看到這裡是去判斷prototypesCurrentlyInCreation擷取到的存放beanName的set中有沒有我們目前的beanName,這裡的prototypesCurrentlyInCreation其實是一個存放Set類型的ThreadLocal。是以很明顯這裡的判斷隻對目前的線程有效,也就是所如果發生了存在beanName的情況必然是目前線程之前在生成這個bean的時候又依賴到目前bean了,也就是說發生依賴循環了。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

5. 委托上一層次的工廠生成bean

我們繼續doGetBean方法的主流程繼續往下看,接下來的流程就是bean第一次被初始化需要執行的建立流程。這一部分的代碼是委托父工廠來擷取目前的bean,為什麼要委托父工廠來生産我們要擷取的bean的?因為***容器的各個層次是互相隔離的***,目前的beanFactory中不一定能夠找到我們要生産的bean的定義,因為有些bean的定義可能會被注冊到上一層次的容器中去。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

我們可以看一下相關容器的類圖,通過這張類圖我們可以很清楚地看到各個容器之間的測試結構,比如我們使用的AnnotationConfigApplicationContext上面就有GenericApplicationContext和AbstractApplicationContext。而不同的容器中會注冊不同的beanDefinition,當我們通過AnnotationConfigApplicationContext去擷取注冊在AbstractApplicationContext容器中的bean的時候,就會執行上面的邏輯,因為在目前的容器裡面找不到我們需要的beanDefinition是以就會擷取上一層次的beanFactory來幫助我們擷取bean對象,當然目前層次容器擷取到的bean對象也隻會存放到目前層次的容器當中。而調用上層次的容器的方法其實就是getBean方法。當然這***是在我們目前容器中找不到指定bean的定義的時候才會委托上層次的工廠來擷取***。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

6. 類型檢查

接下來是标記這個bean是否隻是用作類型檢查而不是真正地想使用這個bean。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

這裡傳入的參數是typeCheckOnly,這個參數是doGetBean方法傳入的,我們看一下源碼上對這個參數的注釋:标記這個執行個體是為了類型檢查而擷取還是為了實際的使用而擷取。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

如果是用作類型檢查的話,我們點開markBeanAsCreated方法檢視一下裡面的邏輯:如果目前的bean沒有被建立或者正在被建立的話就鎖住合并的bean定義緩存池,再判斷一下目前的bean是否還沒有被建立,然後标記目前的bean的合并定義需要被re-merged也就是重新擷取,之後将這個beanName放到bean定義合并緩存池中标記目前bean正在被建立。這裡之是以讓之後的bean執行個體化需要重新擷取bean的合并定義是為了防止類的中繼資料資訊被修改了(比如反射),進而導緻目前擷取的bean合并定義過期了。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

7. 合并父子類的beanDefinition

如果流程執行到這裡來了就表示***目前要擷取的bean的定義在本層次的容器裡面,同時目前的bean也沒有工廠bean來幫助我們來建立,這時候下面的流程就是根據beanDefinition來建立一個bean了***。在doGetBean方法裡面對應的代碼片段如下,先合并目前要擷取bean的父子類的beanDefinition,然後再檢查一下擷取到的合并的定義。比如我們目前的bean繼承了一個抽象類或者是實作了一個接口,那麼在這裡就需要将父類的屬性或者方法或者依賴和我們目前的bean定義合并,通過合并來生成一個新的beanDefinition,通過目前的beanName将這個定義緩存到map中。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

我們點開getMergedLocalBeanDefinition方法,這裡面的邏輯就是先從合并定義緩存中嘗試擷取目前bean的合并定義,如果擷取到了的話就判斷一下目前的這個定義是否需要被重新整理(前面說到的類型檢查),如果不需要的話就直接傳回。如果沒擷取到或者bean定義需要重新整理的話就調用getMergedBeanDefinition方法去擷取合并的定義。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

我們點看getMergedBeanDefinition方法詳細地看一下代碼的處理流程。首先會将mergedBeanDefinitions這個緩存鎖住,然後先嘗試從這個緩存中擷取bean的合并定義,如果擷取到了就直接傳回,如果沒有擷取到就要自己去擷取了。

如果緩存中沒有擷取到或者是要求被重新整理那麼就嘗試從容器中擷取目前bean的父bean定義。如果沒有擷取到的話就調用transformedBeanName來擷取bean的别名之後再用這個别名跟beanName做比較如果不一樣的話根據這個bean的别名遞歸調用目前的方法去擷取父類beanDefinition。如果一樣的話可能目前bean的父類bean的定義不在目前的容器裡面,這時就嘗試擷取上一層次的工廠,通過上一層次的工廠的getMergedBeanDefinition方法來擷取目前bean的父類beanDefinition。

擷取到父類的bean定義之後通過深拷貝複制一個對象,之後通過overrideFrom這個方法來對目前類和父類的屬性進行複制。

之後如果合并的bean定義沒有設定scope的話就設定一個預設的單例作用域。

之後再判斷目前的類是否是原型地并且包含一個内部類,如果是的話就設定其内部類的scope為原型。

之後将合并的bean定義放到mergedBeanDefinitions這個緩存中。

最後會根據之前未合并父bean定義設定合并過的bean定義。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

關于overrideFrom這個合并父子類bean定義的實際方法裡面的邏輯十分簡單這裡我就不說了,隻是非常詳細地把一個BeanDefinition中所有能複制的屬性都複制了一遍,比較重要的例如:類的中繼資料、是否懶加載、屬性、方法、依賴等等。

8. 檢查bean定義

讓我們繼續回到doGetBean方法的主流程繼續往下看,擷取到了合并的bean定義之後需要對這個bean定義進行檢查,檢查的内容就是判斷這個類是不是抽象的,如果是的話就抛出異常。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

9. 判斷依賴循環并初始化依賴的bean

繼續我們的主流程,在擷取并檢查完成合并的bean定義之後,接下來的步驟就是初始化bean了。接下來的這個流程很重要,在我們初始化我們需要的bean之前,我們會從bean定義中擷取目前bean的依賴的bean的name數組,之後依次初始化所有的依賴bean。這裡有一個地方特别重要就是圖中的isDependent方法,這個方法控制了bean的依賴循環,如果目前bean的依賴存在循環依賴的話那麼就抛出BeanCreationException異常。

在調用getBean方法去建立依賴的bean之前,需要先将這個beanName注冊到緩存中,提供給之後的依賴bean的依賴循環檢查。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

這裡isDependent方法很重要,這裡我着重地講一下,首先我們看一下方法的入參,beanName是目前要建立的bean的name,dependentBeanName是目前bean正準備初始化的beanName。這裡再整個邏輯開始之間先鎖住目前層次的dependentBeanMap。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

這個dependentBeanMap是存放目前beanName和這個bean所有的依賴的bean的name的Set的資料結構。這裡就存放了目前bean依賴了哪些bean,比如A->B,A->C那麼在這個緩存中關于beanA存放的資訊就是A->Set(B, C)。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

我們點開isDependent的實際處理方法,這個方法是spring判斷單例bean是否依賴循環的比較核心的方法,同時這個方法第一次讀源碼的時候也不容易了解。

首先這個方法是會被自己遞歸調用的這個要清楚,這個方法的入參有三個,一個是目前的beanName,一個是目前的bean的其中一個依賴的beanName,還有一個記錄目前的整個遞歸流程中已經出現過的beanName的Set。

代碼流程中首先會去判斷alreadySeen中有沒有出現目前的beanName如果出現的話就表示依賴循環。

首先判斷一下目前bean的依賴Set裡面是否包含我們傳入的依賴beanName,如果包含的話就依賴循環了,因為我們回顧一下主流程,目前的依賴bean在執行個體化之前會被注冊到dependentBeanMap中去同時符合beanName->Set(dependentBeanName)的形式,是以如果在這個方法的這個判斷裡面的目前bean的被注冊的依賴bean的Set裡面存在了我們傳入的依賴的beanName的話表示建立的bean依賴了兩個相同的bean,這個應該不算依賴循環了應該說是代碼錯誤了。

之後周遊這個依賴beanName的Set遞歸調用isDependent方法。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

10. 注冊将要被建立的依賴bean

在完成了依賴循環的檢測之後在依賴對象被建立之前需要将這個依賴bean注冊到緩存中,也就是我們上面依賴循環檢測用到的緩存。執行的邏輯也十分簡單,首先是根據beanName映射一個這個bean依賴的beanName的set到map裡面也就是dependentBeanMap,之後再将依賴的beanName放到set裡面。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

11. 遞歸getBean主流程初始化依賴bean

在判斷依賴的bean被允許建立并且被注冊到緩存中之後,就是初始化依賴bean的時候了。這裡的初始化方式是調用getBean方法來初始化這個bean。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

12. 建立bean

完成了這麼多前置的步驟我們終于來到了建立目前bean的流程了。這裡調用的是createBean方法來建立bean的執行個體,和上面擷取緩存中的單例一樣,這裡也是在擷取到執行個體之後調用了getObjectForBeanInstance方法來對執行個體進行進一步的處理。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

讓我們一起來看一下建立bean的方法createBean,通過這個方法上面的注釋我們可以知道這個方法是整個AbstractAutowireCapableBeanFactory類的核心方法,而這個方法主要幹什麼呢?注釋上面也寫了:建立bean執行個體、填充屬性到bean執行個體、調用後置處理器等等。我認為這個方法也是整個spring架構的核心和基礎,因為對bean的處理是整個spring架構最基礎的依賴,沒有了IOC的話spring提供的别的功能比如AOP等等都是空談。

我們先來看一下這個方法的整體執行流程。

  • 首先是擷取bean的運作時類,然後将這個運作時類設定到beanDefinition裡面。
  • 之後會去校驗bean的重寫的方法,并且判斷這些重寫方法有沒有重載,如果沒有的話就設定這個方法不需要進行參數檢查。
  • 在bean在被執行個體化之前調用後置處理器,允許後置處理器這個時候傳回一個代理對象,這裡很重要。
  • 最後是調用doCreateBean方法,這也是實際建立bean的方法,之前的都是準備工作。
【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析
建立bean執行個體之前調用後置處理器

這裡我們詳細看一下resolveBeforeInstantiation方法,這裡首先通過beforeInstantiationResolved這樣的标記狀态來判斷目前的bean有沒有執行過目前的後置處理流程,如果處理過了的話就不處理了。之後判斷目前這個類是不是合成的以及是否存在bean後置處理器,bean是合成的大緻的意思就是目前的運作時類和實際的class檔案加載出來的結果不一樣,這時候spring不對其在建立之前做後置處理。之後就是調用後置處理器來進行處理了,這裡的後置處理器的作用是傳回一個我們期望的目前bean的代理對象,之後的建立流程都是針對于這個代理對象來完成,比如AOP的實作就是通過在這裡傳回bean的代理對象實作的AOP功能。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

我們來看一下擷取bean後置處理器的方法。在這裡通過工廠内部的一個緩存的内部類BeanPostProcessorCache來存放所有的注冊到目前容器中的後置處理器。可以看到這個内部類中有四種類型的bean後置處理器,而擷取這四種類型的bean後置處理器的方式是通過從之前容器啟動的流程中儲存目前容器所有的bean後置處理器的緩存beanPostProcessor中擷取所有的注冊了的beanProcessor,之後根據對這些後置處理器類型做判斷就可以将這些後置處理器分成四類,然後分别放到各自所屬的緩存中。而我們在建立bean執行個體之前我們使用的是InstantiationAwareBeanPostProcessor類型的bean後置處理器。調用後置處理器的邏輯十分簡單,隻是從緩存中擷取beanPostProcessor然後循環執行即可,同時隻要有一個後置處理器的執行結果不為空那麼就直接傳回即傳回了一個代理對象或者是其他的對象(注意我們在容器啟動的時候就已經對bean後置處理器進行過排序了),在這裡就不過多贅述了。

由這裡可以知道spring針對于bean後置處理器的套路總結一下就是:容器啟動的時候将所有的bean後置處理器排序并儲存到緩存中,在使用的時候從這個緩存中拿出所有bean後置處理器根據類型進行分類并放到指定的緩存中,最後根據實際的功能需求進行指定種類的後置處理器的調用。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

12.1 doCreateBean建立bean執行個體

需要注意的是如果經過上面的後置處理器處理之後已經傳回了一個bean執行個體,那麼就執行不到這裡了。

doCreateBean這個方法十分重要,主要的功能有:擷取bean執行個體、填充bean屬性、調用bean後置處理器、調用bean定義合并後置處理器、暴露早期對象、處理依賴的bean、注冊銷毀時需要調用回調的bean。這個方法可以傳回一個真正可以被使用的bean(當然後面還有類型轉換)。

  • 下面我們看一下這個方法的主要的執行流程。首先會從factoryBeanInstanceCache這個我們之前經常提到的工廠bean生産的對象的緩存中嘗試擷取我們需要的bean對象,因為在這個流程中可能有别的線程調用了之前的工廠bean生産bean的流程。如果我們在這個緩存中擷取不到對象的話,我們就需要老老實實的自己去建立一個bean了,這裡調用的是createBeanInstance方法。
  • 通過上面的兩個步驟我們能夠得到一個bean對象,但是這個bean對象還不是可以被使用的,裡面的屬性還沒有被填充以及一些後置處理還沒有被執行。在擷取到bean對象之後會去設定目前的bean定義中的bean的類型為目前bean的類型,之後會擷取bean對象的包裝類也就是Wrapper類。
  • 在上面的步驟完成之後會去調用bean合并定義修改的後置處理器,也就是mergedBeanDefinitionPostProcessors,這個的調用流程就和我們前面說到的bean執行個體化後置處理器類似,也是從緩存中擷取之前被注冊的後置處理器再循環執行即可,這裡就不再贅述。
  • 接下來的步驟就比較重要了spring會去判斷這個bean是否允許被早期依賴,什麼是早期依賴呢?spring在處理依賴循環的時候,都是通過對***依賴對象的引用進行依賴***,也就是說在這個對象被執行個體化(對象隻是被建立但是還沒有被填充屬性等等)之後這個依賴bean就可以被依賴。而這裡還将bean交給了AOP來處理如果目前bean需要被代理的話,這裡會将AOP的代理對象傳回作為這個bean的早期依賴對象。
  • 之後會對bean進行屬性填充,并且調用bean的後置處理器等等回調來對bean進行完善。
  • 最後就是處理目前bean依賴的bean,以及如果這個bean需要在銷毀的時候調用回調的話就将這個bean注冊到緩存中。
【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析
createBeanInstance建立bean的早期執行個體

這裡是spring真正建立bean執行個體的方法,在這個方法裡面的主要流程是:

  • 嘗試通過supplier來擷取bean對象,supplier類似于一個工廠,Supplier接口隻有一個get方法,子類通過實作這個接口來擷取特定的bean對象。在這裡會先去嘗試擷取beanDefinition裡面配置supplier。
  • 嘗試通過工廠方法來擷取bean對象。
  • 通過構造方法來擷取bean對象。
【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析
暴露早期對象

在完成了bean的執行個體化以及後置處理器的處理之後,需要将這個早期對象暴露到緩存中,進而可以解決依賴循環的問題。

  • 首先會去判斷目前的bean是否允許早期依賴,判斷的條件是是否是單例、是否允許環形依賴、是否正在被建立。
【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

我們點開getEarlyBeanReference方法,這個方法可以擷取目前bean需要被暴露的早期對象的引用,為什麼不直接暴露bean的引用呢?因為有些場景,我們在早期對象生成之後可能最後傳回的并不是這個早期對象而是代理對象,比如AOP就可以在這個地方調用其InstantiationAwareBeanPostProcessors類型的bean初始化後置處理器,傳回一個bean的包裝類,而之後spring處理依賴的時候就會依賴于這個代理的包裝類。

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-xIeOiksK-1624972243067)(C:\Users\dell\Desktop\無标題.gif)]

在獲得了早期bean的引用之後會把這個bean暴露到緩存中,在下面的代碼中可以看到,如果單例池中不存在這個bean對象的話,就把這個對象放到三級緩存(singletonFactories)中以及放到被注冊的單例緩存(registeredSingletons)中,然後删除二級緩存(earlySingletonObjects)中的對象。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析
填充bean屬性

在完成了bean的執行個體化之後,目前bean中的一些注入是沒有被處理的,是以在這個步驟中對bean的屬性進行了注入。

  • 首先會調用InstantiationAwareBeanPostProcessors類型的後置處理器,這個處理器執行的目的是在bean的屬性被設定之前修改bean屬性注入的方式。
  • 之後通過判斷依賴的注入方式是byName還是byType來對依賴進行處理,主要是調用getBean方法生成依賴的bean并且将這個beanName注冊到目前bean的依賴緩存中。
  • 再次調用InstantiationAwareBeanPostProcessor類型的後置處理器修改要注入的屬性的值。
  • 進行屬性的可用性檢查。
  • 使用反射注入屬性。
【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析
初始化bean

這裡的初始化bean是指在bean已經被執行個體化以及屬性已經被填充之後,通過調用工廠的回調或者是初始化方法以及後置處理器來對bean進行進一步的處理。

  • 調用invokeAwareMethod方法來對bean的包裝對象進行進一步的屬性設定。
  • 在調用初始化方法之前調用緩存中的bean後置處理器來對bean進行處理。
  • 調用bean定義中配置的初始化方法。
  • 在初始化方法調用完成之後調用bean後置處理器對bean進行處理。
【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析
檢查依賴bean

這裡會先判斷一下目前的bean是不是代理bean,判斷的方式就是根據目前的bean對象和單例池中的bean對象進行引用的比較,如果不一樣的話就需要進一步去檢查判斷目前bean的依賴bean是否都是用作類型檢查的,如果是的話就抛出異常。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析
注冊銷毀時需要調用回調的bean

這個步驟會判斷目前bean時候配置了DestroyMethod或者是DestructionAwareBeanPostProcessor或者是destructionAware,如果是的話表示在這個bean在銷毀的時候需要調用這些方法。在這裡的處理方式是如果需要調用這些方法那麼就将bean對象注冊到disposableBeans緩存中。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析

13. 類型轉換

在獲得了完成的bean對象之後需要檢查bean的實際被使用的類型,因為我們的建立過程中最終擷取的是一個Object類型的對象。這裡的處理方式根據bean的運作時類擷取bean類型然後擷取spring配置的TypeConverter對bean對象的類型進行轉換。

【Spring】bean生命周期、依賴循環、三級緩存源碼詳解getBean流程源碼解析