1、Spring中bean的建立流程
bean建立的簡易流程如下所示:
- 首先調用createBeanInstance方法進行bean的執行個體化
- 然後調用populateBean進行屬性的填充
- 接着調用initializeBean進行後置處理
- 最後調用getSingleton方法添加到單例池中
以上createBeanInstance、populateBean和initializeBean均屬于類org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory,getSingleton方法屬于org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
接下來就看看詳細的流程
2、Spring建立bean的詳細流程
首先我麼來看看使用AnnotationConfigApplicationContext這個配置類加載bean的時候,建立一個bean會走哪些流程:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CO0YmN3gjNlRGMmRDNjZDZ4kzNmZzMzkjN4gjNmJWZ08CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
從上圖中,我們可以知道建立bean最終會調用doCreateBean方法,這個doCreateBean最終完成了建立bean執行個體、進行屬性填充和後置處理的功能!
首先需要知道一點,也就是ApplicationContext中的beanFactory一般是DefaultListableBeanFactory,這點我們從上圖中可以得知,是以我們直接通過這個類來探索Spring中加載bean的過程。
2.1 調試程式
首先,來看一段程式用來協助我們檢視bean的建立流程:
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(AService.class);
rootBeanDefinition.setPropertyValues(new MutablePropertyValues().add("id", "88888888"));
beanFactory.registerBeanDefinition("service", rootBeanDefinition);
/*
可以在此處打斷點進行調試,進而擷取bean的建立流程,到此流程時直接進入即可
*/
beanFactory.getBean("service");
}
public static class AService {
private String id;
public void setId(String id) {
this.id = id;
}
}
通過調試我們可以知道,調用getBean方法會首先調用DefaultListableBeanFactory父類AbstractBeanFactory的doGetBean方法,這點從調試的方法調用資訊就可以看到:
在調用doGetBean的方法中,會進行兩次調用getSingleton,這兩次調用第一次是用于擷取bean,由于此時還不存在這個bean,是以在第二次調用時就用于建立這個bean,接下來看看這兩次調用:
2.2 doGetBean
第一次調用:
這次調用隻向getSingleton中傳入了beanName,此時bean對象還未建立,無法擷取到bean對象,并且bean對象并未處于正在建立的單例集合中,是以會直接傳回null,此時調用的是getSingleton(String beanName, boolean allowEarlyReference)方法
第二次調用:
第二次調用的方法為getSingleton(String beanName, ObjectFactory<?> singletonFactory),這裡使用了lambda表達式傳入了ObjectFactory對象,為什麼是ObjectFactory對象呢,這點可以通過調試的方式看出來,當執行到這個方法以後,會調用getSingleton的另一個重載方法,這個重載方法裡面的參數就是ObjectFactory,也可以接着運作看下去。
2.3 getSingleton方法
接下來我們看看getSingleton方法的實作,這裡調用的是org.springframework.beans.factory.support.DefaultSingletonBeanRegistry的getSingleton方法:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
// 從singletonObjects中擷取beanName對應的bean執行個體化對象,singletonObjects翻譯過來就是單例池
Object singletonObject = this.singletonObjects.get(beanName);
// 如果單例池中不存在該對象,并且該對象為正在建立中的單例對象,那麼就進入循環
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 從earlySingletonObjects中擷取beanName對應的對象
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果擷取到的對象仍然為空的話,并且允許暴露早期的引用,那麼就會進入該循環
if (singletonObject == null && allowEarlyReference) {
// 此時會鎖住singletonObjects這個屬性
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
// 繼續從singletonObjects中擷取該對象
singletonObject = this.singletonObjects.get(beanName);
// 判斷對象是否被成功建立出來,如果建立出來就不再建立,直接傳回,防止該對象重新建立,這裡類似于單例模式的雙重校驗鎖
if (singletonObject == null) {
// 當這個單例對象确實沒有建立出來時,那麼我們就繼續看earlySingletonObjects是否包含該對象
singletonObject = this.earlySingletonObjects.get(beanName);
// 繼續判斷這個對象有沒有被建立出來,也是為了避免重複建立
if (singletonObject == null) {
// 如果singletonObjects和earlySingletonObjects中都沒有這個對象,那就從singletonFactories中擷取到這個對象的工廠
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 如果這個對象工廠不為空就繼續進入下面的流程
if (singletonFactory != null) {
// 調用singletonFactory.getObject()方法擷取到這個對象
singletonObject = singletonFactory.getObject();
// 将這個對象加入到earlySingletonObjects中去
this.earlySingletonObjects.put(beanName, singletonObject);
// 從singletonFactories中移除這個對象,有助于垃圾回收
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
// 最後傳回這個對象
return singletonObject;
}
getSingleton的重載方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
// 首先鎖住singletonObjects對象
synchronized (this.singletonObjects) {
// 從單例池中擷取beanName對應的bean對象
Object singletonObject = this.singletonObjects.get(beanName);
// 如果單例池中不存在該對象,就繼續下面的流程
if (singletonObject == null) {
// 如果目前正處于對象的銷毀狀态則不允許建立對象,并抛出異常
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 + "'");
}
// 将對象加入正在建立的集合
beforeSingletonCreation(beanName);
// 辨別這個對象是否為新建立的對象
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// 從singletonFactory中生産對象,調用傳遞進來的lambda裡面的方法中生産對象
singletonObject = singletonFactory.getObject();
// 證明此對象為新建立的對象
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
// 從正在建立的對象集合中删除該對象
afterSingletonCreation(beanName);
}
// 如果該對象為新建立成功的對象,那麼将這個兌現加入到單例池中
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
// 傳回這個新建立的對象
return singletonObject;
}
}
/**
* 判斷對象是否正在建立,如果是的話将其加入正在建立的集合,否則抛出異常
*/
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
/**
* 将bean對象從正在建立的集合中删除
*/
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}
2.4 createBean方法
當我們繼續執行getSingleton方法時,其中的singletonFactory.getObject()方法會進入到這個lambda表達式中,調用其中的方法,也就是createBean方法。
我們來看看createBean方法,該方法會調用doCreateBean方法,createBean方法會對傳入的RootBeanDefinition做一系列的設定以及初始化操作,然後再傳遞給doCreateBean方法
2.5 doCreateBean方法
在doCreateBean方法中會調用createBeanInstance方法,在該方法中使用适當的執行個體化政策為指定bean建立一個新執行個體:工廠方法、構造函數自動連接配接或簡單執行個體化。
這個方法裡面會推斷我們bean的構造函數,然後進行相應的執行個體化,注意此時還不算是bean對象。如果沒有推斷出來合适的構造函數,會調用無參構造函數将對象執行個體化,由于我們的對象就沒有提供構造函數,是以隻能使用無參構造函數初始化,此時會調用instantiateBean方法:
繼續進入到instantiateBean方法中去,這個方法的作用就是使用無參構造函數為我們執行個體化出來一個普通的對象,建立出來的執行個體化對象會被包裝成一個BeanWrapper對象,最終會傳回給doCreateBean方法,我們繼續來看doCreateBean這個方法。回到這個方法後,我們可以運作可以看到下面這行代碼,這行代碼就是将singletonFactory加入到singletonFactories中去,然後在getSingleton方法中會調用這個對象的getObject方法制造相應的對象。
接着我們繼續向下看,下面就會遇到populateBean和initializeBean這兩個方法了,這兩個方法分别是給bean填充屬性和對bean進行初始化操作的。在屬性填充階段可能會遇到循環依賴,有關循環依賴的問題後續再将。
屬性填充完成後會進行bean的初始化操作
至此,doCreateBean方法算是完成了。接着會繼續調用getSingleton方法,繼續進行bean對象的建立工作。
當對象建立完成以後,會将這個新對象添加到單例池中,此時會調用addSingleton方法。
2.6 addSingleton方法
addSingleton方法屬于類DefaultSingletonBeanRegistry
protected void addSingleton(String beanName, Object singletonObject) {
// 鎖住singletonObjects對象
synchronized (this.singletonObjects) {
// 将bean的名稱及對應的執行個體加入到singletonObjects中去,以後擷取對象也是從單例池中擷取
this.singletonObjects.put(beanName, singletonObject);
// 将singletonFactories和earlySingletonObjects中對應名稱的對象移除,幫助jvm垃圾回收
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
// 在registeredSingletons添加已經完成執行個體化的bean的名稱
// registeredSingletons中記錄了一系列的bean的名稱
this.registeredSingletons.add(beanName);
}
}
當這一系列方法調用完成以後,這個bean的建立流程就結束了。
這裡隻讨論了單例作用域的bean的建立過程,當bean為原型或者其他作用域時可以自己去嘗試調試跟蹤它們對應的流程。
2.7 回到doGetBean
最後我們又回到了doGetBean方法中,當我們擷取完bean對象以後,還需要檢查這個bean對象與給定的類型是否一緻,如果不一緻還需要進行一定的轉換,如果一緻的話則直接傳回這個bean即可。
3、總結
方法調用流程圖
4、循環依賴的解決
通過上面了解了Spring中bean的加載機制,接下來了解spring是如何解決循環依賴的就好辦了!
spring的循環依賴包括構造器循環依賴和setter方法循環依賴,spring隻能解決setter方法的循環依賴,但是不能解決構造器的循環依賴;此外,spring隻能解決bean的作用域為單例的循環依賴,不能解決prototype作用域及其他作用域的循環依賴,這幾點要記住!
首先先看看什麼是循環依賴:
通過上圖我們可以發現,AService中依賴b屬性,而BService中依賴a屬性,這就造成了循環依賴;執行個體化AService後注入屬性時,注入b,此時就要執行個體化BService,對BService進行屬性填充時,又要執行個體化a,這就是典型的循環依賴,看看這個循環依賴是怎麼在代碼中實作的:
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
{
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(AService.class);
// rootBeanDefinition.setScope("prototype");
rootBeanDefinition.setPropertyValues(new MutablePropertyValues().add("b",
new RuntimeBeanReference("bService")));
beanFactory.registerBeanDefinition("aService", rootBeanDefinition);
}
{
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(BService.class);
rootBeanDefinition.setPropertyValues(new MutablePropertyValues().add("a",
new RuntimeBeanReference("aService")));
beanFactory.registerBeanDefinition("bService", rootBeanDefinition);
}
Object service = beanFactory.getBean("aService");
System.out.println(service);
}
public static class AService {
private BService b;
public BService getB() {
return b;
}
public void setB(BService b) {
this.b = b;
}
}
public static class BService {
private AService a;
public AService getA() {
return a;
}
public void setA(AService a) {
this.a = a;
}
}
運作上面這段代碼,我們發現可以正常輸出結果,那麼這就證明spring幫助我們解決了循環依賴,那究竟是怎麼解決的呢,我們來一探究竟(此時我們上面介紹的bean的建立過程就派上了用場)。
我們再來看一個流程圖:
4.1 AService第一次建立流程
我們來看看當遇到循環依賴時是如何建立AService的:
- 首先通過getBean方法擷取AService時,會調用AbstractBeanFactory中的doGetBean方法
- 在doGetBean方法中,首先會調用一次getSingleton方法,此時隻傳bean的名稱,然後根據bean的名稱查詢singletonObjects是否包含該beanName對應的bean執行個體,如果不存在,則去通路singletonFactories,看是否存在對應的ObjectFactory對象,如果還是不存在就退出,這裡由于是第一次建立,是以不會存在的,是以到這裡方法就退出了,傳回了一個null
- 接着就會進行一系列的屬性設定和判斷,然後會重新調用getSingleton方法,此時會調用另一個重載方法getSingleton(String beanName, ObjectFactory<?> singletonFactory),然後使用lambda表達式傳入了ObjectFactory對象接着會在lambda表達式中調用createBean方法
- 調用createBean方法,設定了RootBeanDefinition的一些屬性,然後會接着調用doCreateBean方法
- 在doCreateBean方法中:
- 首先調用createBeanInstance方法建立bean的執行個體,此時會解析bean對應的構造函數,但是不會處理構造函數中的循環依賴,是以構造函數的循環依賴不能解決
- 當建立完bean的執行個體後,會判斷bean是否為單例的,是否允許處理循環依賴,是否是正在建立的bean對象,當這一系列判斷都為true時,會将這個半成品的bean包裝成ObjectFactory對象添加到singletonFactories中
Spring源碼系列:bean生命周期及循環依賴 -
然後就會調用populateBean方法進行屬性的填充
當通過名稱自動注入時:
當通過類型自動注入時:Spring源碼系列:bean生命周期及循環依賴 Spring源碼系列:bean生命周期及循環依賴 Spring源碼系列:bean生命周期及循環依賴 Spring源碼系列:bean生命周期及循環依賴 此時無論通過名稱還是通過類型進行自動注入,都回調用getBean方法,此時就是getBean(“b”),要進入建立bean的流程了Spring源碼系列:bean生命周期及循環依賴
- 此時建立AService的流程到此暫停了,我們要去進行BService的建立流程了!
4.2 BService的建立流程
由于上面的AService建立依賴了BService,是以我們就來建立BService了。建立BService的流程的前三步和建立AService的流程一樣,但是到第四步就有所不同了,是以,這裡忽略了前三步的建立流程,直接到屬性注入這一步:
- BService屬性注入:BService屬性注入a時,會調用getBean(“a”)的方法,此時還是會調用AbstractBeanFactory#getBean方法,然後調用其doGetBean方法。
- 在doGetBean方法中,會調用getSingleton(String beanName, boolean allowEarlyReference)方法,擷取AService的對象,此時AService還沒有建立完,但是我們可以從singletonFactory中擷取到對應的ObjectFactory對象,通過singletonFactory.getObject()方法,可以擷取半成品的bean,将這個bean放入earlySingletonObjects中,并從singletonFactories移除這個beanName對應的ObjectFactory對象,此時就解決了循環依賴的問題
- BService屬性注入完成以後,會調用initializeBean方法進行bean的前置、初始化、後置處理,處理完成後就可以傳回這個bean了
- 這個bean傳回後,又到了getSingleton方法中,這個方法會将新建立的BService加入到singletonObjects中,調用的是addSingleton方法,該方法會清空singletonFactories和earlySingletonObjects中關于該BService對象的那些工廠對象和半成品對象垃圾回收
至此,BService的建立流程就結束了。
4.3 AService第一次建立流程續
BService已經建立完了,是以我們在AService的屬性注入中可以注入這個對象了,将這個對象注入完成以後,會經曆initializeBean的方法,最終在getSingleton方法中調用addSingleton方法将AService對象也加入到singletonObjects中。
4.4 相關問題
為什麼使用singletonObjects、earlySingletonObjects和singletonFactories這三個緩存來解決循環依賴的問題呢?
因為三級緩存中放的是生成具體對象的匿名内部類,他可以生成代理對象,也可以是普通的執行個體對象。
使用三級緩存主要是為了保證不管什麼時候使用的都是一個對象。
假設隻有二級緩存的情況,往二級緩存中放的顯示一個普通的Bean對象,BeanPostProcessor去生成代理對象之後,覆寫掉二級緩存中的普通Bean對象,那麼多線程環境下可能取到的對象就不一緻了。
為什麼getBean的時候才執行getObject方法?為什麼添加工廠的時候不直接執行getObject方法,而是先放到singletonFactories緩存中,再在getSingleton中進行調用呢?
這主要是因為後置處理有時會依賴于某些參數(這些參數指的是,我們有時會在properties配置參數,進行後置處理時可能會依賴這些參數,根據這些參數的不同走不同的流程),此時我們先添加的singletonFactory,然後再進行的屬性填充,如果在添加工廠的時候就getObject那麼此時屬性還未填充,還無法使用這些屬性,是以此時需要添加工廠,然後屬性填充完成以後再調用getObject方法時就可以使用這些屬性了.
5、參考
bilibili-魯班大叔
歡迎通路我的個人部落格哦
風在哪個人部落格