文章目錄
1、轉換對應 beanName
爲什麼需要將傳入的參數 name 轉換成對應的 beanName?
傳入的參數 name 就是 beanName,可能是別名,可能是FactoryBean
怎麼轉換?
- 去除 FactoryBean的修飾符,比如 name="&aa"–> name=“aa”
- 取出指定 alias 所表示的最終 beanName,例如 A 的別名指向 B,B 指向 C,則返回 C
2、緩存中獲取單例 bean
這是什麼意思?
單例在spring的同一個容器內只會被創建一次,後續再獲取bean,就直接從單例緩存中獲取。 這裏是嘗試加載,首先嚐試從緩存中加載,如果加載不成功則再次嘗試從singletonFactories中加載。
爲什麼要這麼做?
在創建單例bean的時候會存在依賴注入的情況,而在創建依賴的時候爲了避免循環依賴,在spring中創建bean的原則是不等bean創建完成 就會將創建bean的ObjectFactory提早加入到緩存中,一旦下一個bean創建需要依賴上一個bean則直接使用ObjectFactory
源碼實現
過程總結
- 嘗試從singletonObjects裏獲取實例
- 如果獲取不到再從earlySingleton-Objects裏面獲取
- 如果還獲取不到,再嘗試從singletonFactories裏面獲取beanName對應的ObjectFactory
- 調用這個ObjectFactory的getObject來創建bean
- 放到earlySingleton-Objects裏
- 從singletonFacotories裏面remove掉這個ObjectFactory
@Override
@Nullable
public Object getSingleton(String beanName) {
//參數true是允許早期依賴
return getSingleton(beanName, true);
}
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//檢查緩存中是否存在實例
Object singletonObject = this.singletonObjects.get(beanName);
//如果緩存爲空,且單例bean正在創建中,則鎖定全局變量
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//如果此bean正在加載,則不處理
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//當某些方法需要提前初始化的時候會直接調用addSingletonFactory把對應的ObjectFactory初始化策略存儲在singletonFactory中
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//調用預先設定的getObject方法
singletonObject = singletonFactory.getObject();
//記錄在緩存中,注意earlySingletonObjects和singletonFactories是互斥的
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
3、從 bean 的實例中獲取對象
得到 bean 的實例後,我們需要調用 getObjectForBeanInstance 方法來檢測正確性(檢測當前 bean 是否是 FactoryBean 類型的 bean ),如果是,調用該bean對應的FactoryBean實例中的getObject()作爲返回值。
getObjectForBeanInstance 方法的過程總結:
- 對 FactoryBean 正確性的驗證
- 對非 FactoryBean 不做任何處理
- 對 bean 進行轉換(將存儲 XML 配置文件的 GernericBeanDefinition 轉換爲 RootBeanDefinition)
- 將從 Factory 中解析 bean 的工作委託給 getObjectFromFactoryBean
代碼
protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
// 如果指定的 name 是工廠相關(以&爲前綴),且 beanInstance 不是 FactoryBean 類型則驗證不通過
if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
}
// 現在我們有個 bean 的實例,這個實例可能會是正常的 bean 或者是 FactoryBean
// 如果是 FactoryBean 我們使用它創建實例,但如果用戶想直接獲取工廠實例而不是工廠的 getObject 對應的實例
// 那麼傳入的 name應該加入前綴 &
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
// 加載 FactoryBean
Object object = null;
if (mbd == null) {
//從緩存中加載 Bean
object = getCachedObjectForFactoryBean(beanName);
}
//讓Bean工廠生產給定名稱的Bean對象實例
if (object == null) {
// 到這裏已經明確知道 beanInstance 一定是 FactoryBean 類型
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
//在所有已經加載的類中檢測是否定義 beanName
if (mbd == null && containsBeanDefinition(beanName)) {
//將存儲 XML 配置文件的 GenericBeanDefinition 轉換爲 RootBeanDefinition
//從容器中獲取指定名稱的Bean定義,如果繼承基類,則合併基類相關屬性
mbd = getMergedLocalBeanDefinition(beanName);
}
//是否是用戶定義而不是應用程序本身定義的
boolean synthetic = (mbd != null && mbd.isSynthetic());
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}
4、獲取單例
過程
(1)檢查緩存是否已經加載過
(2)如果沒有加載,則記錄beanName的正在加載狀態
(3)加載單例前記錄加載狀態。
可能你會覺得beforeSingletonCreation方法是個空實現,裏面沒有任何邏輯,但其實這個函數中做了一個很重要的操作:記錄加載狀態,也就是通過this.singletonsCurrentlyInCreation.add(beanName)將當前正要創建的bean記錄在緩存中,這樣便可以對循環依賴進行檢測。
(4)通過調用參數傳入的ObjectFactory的個體Object方法實例化bean
(5)加載單例後的處理方法調用
同步驟3的記錄加載狀態相似,當bean加載結束後需要移除緩存中對該bean的正在加載狀態的記錄。
(6)將結果記錄至緩存並刪除加載bean過程中所記錄的各種輔助狀態
(7)返回處理結果
源碼
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
// 全局變量需要同步
synchronized (this.singletonObjects) {
// 首先檢查對應的bean是否已經加載過,因爲singleton模式其實就是複用已創建的bean
Object singletonObject = this.singletonObjects.get(beanName);
// 如果爲空,纔可以進行singleton的bean的初始化
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 {
// 初始化bean
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;
}
}
5、準備創建 bean
過程
(1)設置class屬性或者根據className來解析class
(2)驗證MethodOverride屬性進行驗證及標記
在上述代碼中有一個方法:mbd.prepareMethodOverrides();
其實在Spring中確實沒有override-method這樣的配置,但是我們前面說過,在Spring配置中是存在lookup-method和replace-method的,而這個兩個配置的加載其實就是將配置統一存放在BeanDefinition中的methodOverrides屬性裏,而這個函數的操作也就是針對於這兩個配置的。
(3)應用初始化前的後處理器,解析指定bean是否存在應用初始化前的短路操作
(4)創建bean
代碼
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
if (logger.isDebugEnabled()) {
logger.debug("Creating instance of bean '" + beanName + "'");
}
RootBeanDefinition mbdToUse = mbd;
// 鎖定class,根據設置的class屬性或者根據className來解析class
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
// 驗證及準備覆蓋的方法
try {
// 處理override屬性
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),beanName, "Validation of method overrides failed", ex);}
try {
// 給 BeanPostProcessors 一個機會來返回代理來替代真正的實例(實例化的前置處理)
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);}
try {
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { throw ex; }
catch (Throwable ex) {throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);}
}
6、循環依賴
循環依賴 vs 循環調用
循環依賴:A 引用 B,B 引用 C,C 引用 A(循環引用)
循環調用:方法間的環調用,無法解決,除非有終結條件,否則就是死循環,最終導致內存溢出。
Spring 如何解決循環依賴
1、構造器
無法解決,只能拋異常(BeanCurrentlyInCreationException)
如在創建TestA類時,構造器需要TestB類,那將去創建TestB,在創建TestB類時又發現
需要TestC類,則又去創建TestC,最終在創建TestC時發現又需要TestA,從而形成一個環,
沒辦法創建。
spring容器將每一個正在創建的bean標識符放在一個“當前創建bean池"中,bean標識
符在創建過程中將一直保持在這個池中,因此如果在創建bean過程中發現自己已經在“當前
創建bean池”裏時,將拋出BeanCurrentlyInCreationException異常表示循環依賴;而對於創建完畢的bean將從“當前創建bean池”中清除掉。
測試
<bean id="testA" class="com.bean.TestA">
<constructor-arg index="0" ref="testB"/>
</bean>
<bean id="testB" class="com.bean.TestB">
<constructor-arg index="0" ref="testC"/>
</bean>
<bean id="testC" class="com.bean.TestC">
<constructor-arg index="0" ref="testA"/>
</bean>
過程
1、Spring容器創建"testA”bean,首先去“當前創建bean池”查找是否當前bean正在創建,如果沒發現,則繼續準備其需要的構造器參數"testB”,並將"testA”標識符放到“當前創建bean池”。
2、Spring容器創建"testB”bean,首先去“當前創建bean池"查找是否當前bean正在創建,如果沒發現,則繼續準備其需要的構造器參數"testc”,並將"testB”標識符放到“當前創建bean池”。
3、Spring容器創建"testc”bean,首先去“當前創建bean池”查找是否當前bean正在創建,如果沒發現,則繼續準備其需要的構造器參數"testA”,並將"testC”標識符放到“當前創建bean池”。
4、到此爲止Spring容器要去創建"testA”bean,發現該bean標識符在“當前創建bean
池”中,因爲表示循環依賴,拋出BeanCurrentlyInCreationException。
2、setter
what:通過Spring容器提前暴露剛完成構造器注入但未完成其他步驟(如setter注入)的bean來完成的
how:解決單例作用域的bean循環依賴。通過提前暴露一個單例工廠方法,從而使其他bean能引用到該bean。
具體步驟:
1、Spring容器創建單例"testA”bean,首先根據無參構造器創建bean,並暴露一個"ObjectFactory”用於返回一個提前暴露一個創建中的bean,並將"testA”標識符放到“當前創建bean池”,然後進行setter注入"testB”
2、Spring容器創建單例"testB”bean,首先根據無參構造器創建bean,並暴露一個"ObjectFactory”用於返回一個提前暴露一個創建中的bean,並將"testB”標識符放到“當前創建bean池”,然後進行setter注入"circle”
3、Spring容器創建單例"testC”bean,首先根據無參構造器創建bean,並暴露一個"ObjectFactory”用於返回一個提前暴露一個創建中的bean,並將"testc”標識符放到“當前創建bean池",然後進行setter注入“testA”。進行注入"testA”時由於提前暴露了"ObjectFactory"工廠,從而使用它返回提前暴露一個創建中的bean。
4、最後在依賴注入"testB”和"testA”,完成setter注入。
3、prototype範圍的依賴處理
對於 “prototype” 作用域 bean,Spring 容器無法完成依賴注入,因爲Spring容器不進行緩存 “prototype” 作用域的 bean,因此無法提前暴露一個創建中的 bean 。
對於 “singleton” 作用域 bean,可以通過 "setAIIowCircuIarReferences(false);” 來禁用循環引用。
7、創建 bean
7.1 創建 bean 的實例(實例化)
實例化邏輯
- 根據 RootBeanDefinition 中的配置生成 bean 的實例
- 解析構造函數
- 進行構造函數的實例化
Spring 在根據參數和類型去判斷最終會使用哪個構造函數進行實例化,但判斷過程比較耗性能,因此採用緩存機制
實例化的兩種情況:
1.通用的實例化(instantiateBean)
直接調用實例化策略
2.帶有參數的實例化(autowireConstructor)
2.1 構造函數參數的確定
2.1.1 根據參數 explicitArgs 判斷(不爲空,即爲構造函數的參數)
2.1.2 緩存中獲取(緩存中的參數可能是初始類型,也可能是最終類型,因此要經過類型轉換器的過濾)
2.1.3 配置文件獲取(信息保存在 BeanDefinition)
2.2 構造函數的確定(構造函數、參數名稱、參數類型、參數值)
(匹配對應構造函數的方法:參數個數)
2.2.1 按照 public 、非 public 構造函數的參數數量排序
2.2.2 獲取參數名稱:註解、工具類 ParameterNameDiscoverer
2.3 根據確定的構造函數轉換對應的參數類型(類型轉換器:Spring 提供、用戶自定義)
2.4 構造函數不確定性的驗證(父子關係情況)
2.5 根據 實例化策略 + 得到的構造函數 + 構造函數參數 --> 實例化 Bean
2.1、2.2 是 基礎條件,2.5是組合
實例化策略:
1-用戶沒有使用需要動態改變的方法(replace、lookup),則使用反射的方式
2-否則,使用動態代理方式,將包含兩個特性所對應的邏輯的攔截增強器社設置進去
7.2 記錄創建 bean 的 ObjectFactory(處理循環依賴)
處理循環依賴的解決辦法:
在 B 中創建依賴 A 時,通過 ObjectFactory 提供的實例化方法來中斷 A 中的屬性填充,使 B 中持有的 A 僅僅是剛剛初始化並沒有填充任何屬性的 A,這初始化 A 的步驟是在最開始創建 A 的時候進行的,由於 A 與 B 中的 A 所表示的屬性地址是一樣的,所以在 A 中創建好的屬性填充自然可以通過 B 中的 A 獲得。
7.3 屬性注入(填充)
處理流程
- postProcessAfterInstantiation 控制程序是否繼續進行填充
- 根據注入類型(byName、byType)提取依賴的 bean,統一存入 PropertyValues中
- 在屬性獲取完畢填充前,對屬性再次處理(eg:對屬性的驗證)
- 將所有 PropertyValues 中的屬性填充至 BeanWrapper
獲取注入屬性:
(1)autowireByName
在傳入的參數 MutablePropertyValues 中找出已經加載的 bean,並遞歸實例化,進而加入到 MutablePropertyValues
(2)autowireByType
- 嘗試使用解析器進行解析
- 對於集合類型,不在解析範圍,需要再次對不同集合類型進行不同情況的處理
應用到已經實例化的 bean:
applyPropertyValues
- 獲取所有屬性
- 獲取對應的解析器
- 遍歷屬性,將屬性轉換爲對應類的對應屬性的類型
7.4 初始化 bean(進行用戶設定的初始化方法的調用)
- 激活 Aware 方法(實現 Aware 接口的 bean 在被初始化之後,可以取得相對應的資源<BeanFactory實例,ApplicationContext實例>)
- 處理器的應用(BeanPostProcessor,給用戶充足的權限更改 / 擴展Spring)
- 激活自定義的 init 方法
7.5 註冊 DisposableBean(銷燬方法)
銷燬方法的擴展入口
銷燬方法還可以通過:配置屬性 destroy-method 方法