Spring IOC 容器源碼分析 - 創建單例 bean 的過程
1. 簡介
在上一篇文章中,我比較詳細的分析了獲取 bean 的方法,也就是getBean(String)
的實現邏輯。對於已實例化好的單例 bean,getBean(String) 方法並不會再一次去創建,而是從緩存中獲取。如果某個 bean 還未實例化,這個時候就無法命中緩存。此時,就要根據 bean 的配置信息去創建這個 bean 了。相較於getBean(String)
方法的實現邏輯,創建 bean 的方法createBean(String, RootBeanDefinition, Object[])
及其所調用的方法邏輯上更為復雜一些。關於創建 bean 實例的過程,我將會分幾篇文章進行分析。本篇文章會先從大體上分析 createBean(String, RootBeanDefinition, Object[])
好了,其他的不多說,直接進入正題吧。
2. 源碼分析
2.1 創建 bean 實例的入口
在正式分析createBean(String, RootBeanDefinition, Object[])
方法前,我們先來看看 createBean 方法是在哪裏被調用的。如下:
public T doGetBean(...) {
// 省略不相關代碼
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance (sharedInstance, name, beanName, mbd);
}
// 省略不相關代碼
}
上面是 doGetBean 方法的代碼片段,從中可以發現 createBean 方法。createBean 方法被匿名工廠類的 getObject 方法包裹,但這個匿名工廠類對象並未直接調用 getObject 方法。而是將自身作為參數傳給了getSingleton(String, ObjectFactory)
方法,那麽我們接下來再去看看一下getSingleton(String, ObjectFactory) 方法的實現。如下:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "‘beanName‘ must not be null");
synchronized (this.singletonObjects) {
// 從緩存中獲取單例 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 + "‘");
}
/*
* 將 beanName 添加到 singletonsCurrentlyInCreation 集合中,
* 用於表明 beanName 對應的 bean 正在創建中
*/
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<Exception>();
}
try {
// 通過 getObject 方法調用 createBean 方法創建 bean 實例
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
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;
}
// 將 beanName 從 singletonsCurrentlyInCreation 移除
afterSingletonCreation(beanName);
}
if (newSingleton) {
/*
* 將 <beanName, singletonObject> 鍵值對添加到 singletonObjects 集合中,
* 並從其他集合(比如 earlySingletonObjects)中移除 singletonObject 記錄
*/
addSingleton(beanName, singletonObject);
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
上面的方法邏輯不是很復雜,這裏簡單總結一下。如下:
- 先從 singletonObjects 集合獲取 bean 實例,若不為空,則直接返回
- 若為空,進入創建 bean 實例階段。先將 beanName 添加到 singletonsCurrentlyInCreation
- 通過 getObject 方法調用 createBean 方法創建 bean 實例
- 將 beanName 從 singletonsCurrentlyInCreation 集合中移除
- 將
從上面的分析中,我們知道了 createBean 方法在何處被調用的。那麽接下來我們一起深入 createBean 方法的源碼中,來看看這個方法具體都做了什麽事情。
2.2 createBean 方法全貌
createBean 和 getBean 方法類似,基本上都是空殼方法,只不過 createBean 的邏輯稍微多點,多做了一些事情。下面我們一起看看這個方法的實現邏輯,如下:
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
if (logger.isDebugEnabled()) {
logger.debug("Creating instance of bean ‘" + beanName + "‘");
}
RootBeanDefinition mbdToUse = mbd;
// 解析 bean 的類型
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
try {
// 處理 lookup-method 和 replace-method 配置,Spring 將這兩個配置統稱為 override method
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}
try {
// 在 bean 初始化前應用後置處理,如果後置處理返回的 bean 不為空,則直接返回
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
// 調用 doCreateBean 創建 bean
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean ‘" + beanName + "‘");
}
return beanInstance;
}
上面的代碼不長,代碼的執行流程比較容易看出,這裏羅列一下:
- 解析 bean 類型
- 處理 lookup-method 和 replace-method 配置
- 在 bean 初始化前應用後置處理,若後置處理返回的 bean 不為空,則直接返回
- 若上一步後置處理返回的 bean 為空,則調用 doCreateBean 創建 bean 實例
下面我會分節對第2、3和4步的流程進行分析,步驟1的詳細實現大家有興趣的話,就自己去看看吧。
2.2.1 驗證和準備 override 方法
當用戶配置了 lookup-method 和 replace-method 時,Spring 需要對目標 bean 進行增強。在增強之前,需要做一些準備工作,也就是 prepareMethodOverrides 中的邏輯。下面來看看這個方法的源碼:
public void prepareMethodOverrides() throws BeanDefinitionValidationException {
MethodOverrides methodOverrides = getMethodOverrides();
if (!methodOverrides.isEmpty()) {
Set<MethodOverride> overrides = methodOverrides.getOverrides();
synchronized (overrides) {
// 循環處理每個 MethodOverride 對象
for (MethodOverride mo : overrides) {
prepareMethodOverride(mo);
}
}
}
}
protected void prepareMethodOverride(MethodOverride mo) throws BeanDefinitionValidationException {
// 獲取方法名為 mo.getMethodName() 的方法數量,當方法重載時,count 的值就會大於1
int count = ClassUtils.getMethodCountForName(getBeanClass(), mo.getMethodName());
// count = 0,表明根據方法名未找到相應的方法,此時拋出異常
if (count == 0) {
throw new BeanDefinitionValidationException(
"Invalid method override: no method with name ‘" + mo.getMethodName() +
"‘ on class [" + getBeanClassName() + "]");
}
// 若 count = 1,表明僅存在已方法名為 mo.getMethodName(),這意味著方法不存在重載
else if (count == 1) {
// 方法不存在重載,則將 overloaded 成員變量設為 false
mo.setOverloaded(false);
}
}
上面的源碼中,prepareMethodOverrides
方法循環調用了prepareMethodOverride
方法,並沒其他的太多邏輯。主要準備工作都是在 prepareMethodOverride 方法中進行的,所以我們重點關註一下這個方法。prepareMethodOverride 這個方法主要用於獲取指定方法的方法數量 count,並根據 count 的值進行相應的處理。count = 0 時,表明方法不存在,此時拋出異常。count = 1 時,設置 MethodOverride 對象的overloaded
成員變量為 false。這樣做的目的在於,提前標註名稱mo.getMethodName()
的方法不存在重載,在使用 CGLIB 增強階段就不需要進行校驗,直接找到某個方法進行增強即可。
上面的方法沒太多的邏輯,比較簡單,就先分析到這裏。
2.2.2 bean 實例化前的後置處理
後置處理是 Spring 的一個拓展點,用戶通過實現 BeanPostProcessor 接口,並將實現類配置到 Spring 的配置文件中(或者使用註解),即可在 bean 初始化前後進行自定義操作。關於後置處理較為詳細的說明,可以參考我的了一篇文章Spring IOC 容器源碼分析系列文章導讀,這裏就不贅述了。下面我們來看看 createBean 方法中的後置處理邏輯,如下:
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
Object bean = null;
// 檢測是否解析過,mbd.beforeInstantiationResolved 的值在下面的代碼中會被設置
if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
/*
* 關於 synthetic,簡單說一下我的理解,如下:
* synthetic 字面意思是“合成的”,我覺得這個字段可能用於表示 mbd 對應的 bean 是否被 AOP
* 增強過,而 AOP 是通過後置處理增強 bean 的。所以如果 bean 被增強過,也就是
* synthetic = true。此時,不應該再次對 bean 進行增強。
*/
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
Class<?> targetType = determineTargetType(beanName, mbd);
if (targetType != null) {
// 應用前置處理
bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
if (bean != null) {
// 應用後置處理
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}
}
}
// 設置 mbd.beforeInstantiationResolved
mbd.beforeInstantiationResolved = (bean != null);
}
return bean;
}
protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
// InstantiationAwareBeanPostProcessor 一般在 Spring 框架內部使用,不建議用戶直接使用
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
// bean 初始化前置處理
Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
if (result != null) {
return result;
}
}
}
return null;
}
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
// bean 初始化後置處理
result = beanProcessor.postProcessAfterInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}
在 resolveBeforeInstantiation 方法中,當前置處理方法返回的 bean 不為空時,後置處理才會被執行。前置處理器是 InstantiationAwareBeanPostProcessor 類型的,該種類型的處理器一般用在 Spring 框架內部,比如 AOP 模塊中的AbstractAutoProxyCreator
抽象類間接實現了這個接口中的 postProcessBeforeInstantiation
方法,所以 AOP 可以在這個方法中生成為目標類的代理對象。不過我在調試的過程中,發現 AOP 在此處生成代理對象是有條件的。一般情況下條件都不成立,也就不會在此處生成代理對象。至於這個條件為什麽不成立,因 AOP 這一塊的源碼我還沒來得及看,所以暫時還無法解答。等我看過 AOP 模塊的源碼後,我再來嘗試分析這個條件。
2.2.3 調用 doCreateBean 方法創建 bean
這一節,我們來分析一下doCreateBean
方法的源碼。在 Spring 中,做事情的方法基本上都是以do
開頭的,doCreateBean 也不例外。那下面我們就來看看這個方法都做了哪些事情。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
/*
* BeanWrapper 是一個基礎接口,由接口名可看出這個接口的實現類用於包裹 bean 實例。
* 通過 BeanWrapper 的實現類可以方便的設置/獲取 bean 實例的屬性
*/
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
// 從緩存中獲取 BeanWrapper,並清理相關記錄
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
/*
* 創建 bean 實例,並將實例包裹在 BeanWrapper 實現類對象中返回。createBeanInstance
* 中包含三種創建 bean 實例的方式:
* 1. 通過工廠方法創建 bean 實例
* 2. 通過有參構造方法方法創建 bean 實例
* 3. 通過無參構造方法方法創建 bean 實例
*
* 若 bean 的配置信息中配置了 lookup-method 和 replace-method,則會使用 CGLIB
* 增強 bean 實例。關於這個方法,後面會專門寫一篇文章介紹,這裏先說這麽多。
*/
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 此處的 bean 可以認為是一個原始的 bean 實例,暫未填充屬性
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
mbd.resolvedTargetType = beanType;
// 這裏又遇到後置處理了,此處的後置處理是用於處理已“合並的 BeanDefinition”。關於這種後置處理器具體的實現細節就不深入理解了,大家有興趣可以自己去看
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
/*
* earlySingletonExposure 是一個重要的變量,這裏要說明一下。該變量用於表示是否提前暴露
* 單例 bean,用於解決循環依賴。earlySingletonExposure 由三個條件綜合而成,如下:
* 條件1:mbd.isSingleton() - 表示 bean 是否是單例類型
* 條件2:allowCircularReferences - 是否允許循環依賴
* 條件3:isSingletonCurrentlyInCreation(beanName) - 當前 bean 是否處於創建的狀態中
*
* earlySingletonExposure = 條件1 && 條件2 && 條件3
* = 單例 && 是否允許循環依賴 && 是否存於創建狀態中。
*/
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean ‘" + beanName +
"‘ to allow for resolving potential circular references");
}
// 添加工廠對象到 singletonFactories 緩存中
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
// 獲取早期 bean 的引用,如果 bean 中的方法被 AOP 切點所匹配到,此時 AOP 相關邏輯會介入
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
Object exposedObject = bean;
try {
// 向 bean 實例中填充屬性,populateBean 方法也是一個很重要的方法,後面會專門寫文章分析
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
/*
* 進行余下的初始化工作,詳細如下:
* 1. 判斷 bean 是否實現了 BeanNameAware、BeanFactoryAware、
* BeanClassLoaderAware 等接口,並執行接口方法
* 2. 應用 bean 初始化前置操作
* 3. 如果 bean 實現了 InitializingBean 接口,則執行 afterPropertiesSet
* 方法。如果用戶配置了 init-method,則調用相關方法執行自定義初始化邏輯
* 4. 應用 bean 初始化後置操作
*
* 另外,AOP 相關邏輯也會在該方法中織入切面邏輯,此時的 exposedObject 就變成了
* 一個代理對象了
*/
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 若 initializeBean 方法未改變 exposedObject 的引用,則此處的條件為 true。
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// 下面的邏輯我也沒完全搞懂,就不分析了。見諒。
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name ‘" + beanName + "‘ has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"‘getBeanNamesOfType‘ with the ‘allowEagerInit‘ flag turned off, for example.");
}
}
}
}
try {
// 註冊銷毀邏輯
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
上面的註釋比較多,分析的應該比較詳細的。不過有一部分代碼我暫時沒看懂,就不分析了,見諒。下面我們來總結一下 doCreateBean 方法的執行流程吧,如下:
- 從緩存中獲取 BeanWrapper 實現類對象,並清理相關記錄
- 若未命中緩存,則創建 bean 實例,並將實例包裹在 BeanWrapper 實現類對象中返回
- 應用 MergedBeanDefinitionPostProcessor 後置處理器相關邏輯
- 根據條件決定是否提前暴露 bean 的早期引用(early reference),用於處理循環依賴問題
- 調用 populateBean 方法向 bean 實例中填充屬性
- 調用 initializeBean 方法完成余下的初始化工作
- 註冊銷毀邏輯
doCreateBean 方法的流程比較復雜,步驟略多。由此也可了解到創建一個 bean 還是很復雜的,這中間要做的事情繁多。比如填充屬性、對 BeanPostProcessor 拓展點提供支持等。以上的步驟對應的方法具體是怎樣實現的,本篇文章並不打算展開分析。在後續的文章中,我會單獨寫文章分析幾個邏輯比較復雜的步驟。有興趣的閱讀的朋友可以稍微等待一下,相關文章本周會陸續進行更新。
3. 總結
到這裏,createBean 方法及其所調用的方法的源碼就分析完了。總的來說,createBean 方法還是比較復雜的,需要多看幾遍才能理清一些頭緒。由於 createBean 方法比較復雜,對於以上的源碼分析,我並不能保證不出錯。如果有寫錯的地方,還請大家指點迷津。畢竟當局者迷,作為作者,我很難意識到哪裏寫的有問題。
好了,本篇文章到此結束。謝謝閱讀。
參考
- 《Spring 源碼深度解析》- 郝佳
本文在知識共享許可協議 4.0 下發布,轉載需在明顯位置處註明出處
作者:coolblog.xyz
本文同步發布在我的個人博客:http://www.coolblog.xyz
本作品采用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。
Spring IOC 容器源碼分析 - 創建單例 bean 的過程