閱讀之前要注意的東西:本文就是主打流水賬式的原始碼閱讀,主導的是一個參考,主要內容需要看官自己去原始碼中驗證。全系列文章基於 spring 原始碼 5.x 版本。
寫在開始前的話:
閱讀spring 原始碼實在是一件龐大的工作,不說全部內容,單就最基本核心部分包含的東西就需要很長時間去消化了:
實際上我在部落格里貼出來的還只是一部分內容,更多的內容,我放在了個人,fork自 spring 官方原始碼倉了; 而且對原始碼的學習,必須是要跟著實際程式碼層層遞進的,不然只是乾巴巴的文字味同嚼蠟。
這個倉設定的公共倉,可以直接拉取。
本文以走馬觀花的姿態,簡單分析了一波 spring 容器建立bean的大致環節,後續將通過單獨的文章進行細講其中的沒個環節。
本文將包含後文的連結,根據感興趣的內容自取即可。
我們已經知道了spring 是怎麼解析標籤的。
現在我們解析完標籤並註冊到 BeanFactoryRegistry 介面進行管理了,接下來我們要用解析的結果建立bean了
記得開篇說的那兩行程式碼麼:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class XmlBeanFactoryTest {
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("bean.xml"));
Object object = beanFactory.getBean("action");
}
現在正式進入第二行程式碼的流程:
Object object = beanFactory.getBean("action");
同理 【Shift + Ctrl + 滑鼠左鍵】 嘗試找到 getBean 的實現類,這裡發現有多個符合條件的實現類;
再解析 XmlBeanFactory 的類圖,那麼顯而易見,我們應該看 AbstractBeanFactory 類的 getBean() 方法
然後再層層遞進,我們來到了 AbstractBeanFactory.doGetBean() 方法,這個方法的方法體非常長,如果不考慮其中細節,我們只需要講解這一個方法大致就可以知道 BeanFactory.getBean() 最基本的實現,及其重要環節。
實際上在spring 的5.x版本中, doGetBean()方法體非常的長, 應該在 150 行左右了,或許螢幕前的你會嗤之以鼻,就這?
如果平時有嚴格講究clean code 規範,那麼 超過 50 行的方法都是不合理的。(剛畢業時在某家公司見過 2700 多行的方法,雖然離職許久了,但是我敢肯定時至今日,那些程式碼必定還在"成長"。)
下邊我會貼一串處理後的虛擬碼,主要用來展示 doGetGean() 方法的骨架:
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// 1. 轉換/提取beanName name 可能為:FactoryBean 、 別名alias 等
String beanName = transformedBeanName(name);
Object bean;
// 2. 嘗試直接從快取中獲取,或者從 singletonFactories 中的 ObjectFactory 獲取
// - 檢查快取中或者範例工廠中是否有對應範例
Object sharedInstance = getSingleton(beanName); // 第一次嘗試從單例bean池中獲取快取的 bean 範例
if (sharedInstance != null && args == null) {
// && 2.1 如果 bean 原型是FactoryBean 型別,則通過其 getObject() 方法生成真正的 bean
// && 2.2 否則直接返回 sharedInstance 本身作為: 待獲取的bean
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
// 3. 快取中不存在,那麼就要從頭再來了, 從前文 xml 標籤解析到的 BeanDefinition 開始,逐步生成真正的 bean 物件
// 3.1 存在無法消解的迴圈參照
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
// 3.2 parentBeanFactory 檢查
// parentBeanFactory 不為空 && 當前載入的XML中不包含 beanName 對映的類時,會嘗試從 parentBeanFactory 獲取
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// 3.2.1 當前工廠沒能獲取到相關 bean 向 parentBeanFactory 詢問
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
} else if (args != null) {
return (T) parentBeanFactory.getBean(nameToLookup, args);
} else {
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
// 4. 暫停一下, 能走到這一步說明:
// 4.1 第一,從快取取不到,那麼是第一次載入當前bean;
// 4.2 第二,沒有進入上述的 parentBeanFactory 的流程裡,說明 當前 的BeanFactory 裡有帶獲取bean 的定義。
// 5 類比 * 提前暴露,如果該設定生效,通過檢查則,視為該 bean 已經建立
if (!typeCheckOnly) {
// 標記該 bean 已經被建立過
markBeanAsCreated(beanName);
}
// 6. 正式進入,從零開始建立一個 bean 的過程
try {
// 6.1 對 bean 的後續處理都是針對 RootBeanDefinition 進行的,所以需要:
// 將儲存的 XML 組態檔的 GenericBeanDefinition 轉化為 RootBeanDefinition --> ChildBeanDefinition
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); // 老熟人了,第一個系列的文章講的就是怎麼得到她
// 6.2 校驗 mbd 對應的類是否為抽象類,[ 當前程式碼版本: spring 5.1 ]
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
// 6.3 獲取參照關係, 範例化依賴的 bean [需要spring容器注入的依賴]
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) { // 無法處理的, 迴圈依賴拋異常 TODO
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName); // 記錄bean的name 記錄他們之間的參照關係,被銷燬時根據關係去銷燬
try {
getBean(dep); // 遞迴建立當前bean,依賴的其它 bean
} catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
// 7. 建立bean自身
// 它依賴的 bean [ 載入/建立 ] 完畢, 最終到了載入其本身的時刻 (根據它的 beanDefinition 建立真正的 bean 範例)
if (mbd.isSingleton()) {
// 7.1 單例模式
// 第二次嘗試從單例bean快取池中獲取該bean範例;若無法從快取中獲取(未被載入),從頭開始載入該 bean 的範例
// 具體載入行為由 createBean()方法負責
// 回撥 getObject().createBean()前: 範例化前的準備工作 -- before[PostProcessor(後置處理器)]
// 回撥 getObject().createBean()後: 範例化完後的補充工作 -- after[PostProcessor(後置處理器)]
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
try {
// 準備完成後回撥 由子類 AbstractAutowireCapableBeanFactory 實現方法
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
// 出錯,單例工廠銷燬該 bean
destroySingleton(beanName);
}
}
});
// 返回對應的範例 有些情況並不直接返回 範例本身,而是返回 <指定方法> <返回的範例>
// <指定方法> : 實現 《特定工廠介面》 的 《工廠範例》 的某個方法
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
// 7.2 prototype 原型模式, 範例化的手段: 直接建立新的bean
Object prototypeInstance = null;
try {
// 7.2.1 標記 bean 正在被建立
beforePrototypeCreation(beanName);
// 7.2.2 進入建立流程
prototypeInstance = createBean(beanName, mbd, args);
} finally {
// 7.2.3 標記的取消: 正在建立 (7.2.1 的逆向操作)
afterPrototypeCreation(beanName);
}
// 返回對應的bean範例,如果是 FactoryBean 時, 返回其 getObject() 方法的返回值 TODO
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
// 根據設定的 scope 範例化 bean (除單例-singleton、原型-prototype 之外)
String scopeName = mbd.getScope();
Scope scope = this.scopes.get(scopeName);
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
});
// 返回對應的範例 有些情況並不直接返回 範例本身,而是返回 <指定方法> <返回的範例>
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
} catch (IllegalStateException ex) { }
}
} catch (BeansException ex) {
// 發生異常後,清理現場
cleanupAfterBeanCreationFailure(beanName);
}
}
// 檢查生成的bean型別是否符合實際需求
if (requiredType != null && !requiredType.isInstance(bean)) {
try {
T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
return convertedBean;
} catch (TypeMismatchException ex) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
return (T) bean;
}
關鍵詞:
createBean(beanName, mbd, args);
文中一共三處,你可以仔細觀察這三處 createBean() 所處的語境:
第一次是 單例的場景, createBean() 方法的返回值,被一個單例處理的方法接收。它會根據 bean 的名稱判斷是否已經範例化過了,這裡為了保證單例,甚至還用了雙重鎖機制。
第二次是原型模式, 也就是建立物件時直接new, 你看這裡 createBean() 的上下文語境,沒有被任何的校驗操作環繞,直接就返回了。
第三次出現,是為了範例化上述兩種型別之外的 bean, 這裡 createBean() 被 Scope.get() 方法接收,可以確定的是這裡必定會發生,根據 bean的Scope作用域而進行的校驗。
沒錯上邊展示的就是 "骨架", 往簡單了說,getBean() 所做的事情不外乎上邊提到的內容。
但是實際上呢,createBean() 的過程中還有許許多多重要的操作需要一一道來,後續章節將圍繞上述的 "骨架" 進行展開。
後邊的章節可以看作是一個目錄,如果內容比較多,我會通過一個外部連結單獨展示相關的內容。若篇幅較為短小,則會直接在下邊呈現。
第三節介紹的內容將服務於本節接下來的內容,接下來我們將回顧 doGetBean 的整個流程,然後對如下列出的環節進行或詳細或粗略的介紹。
原始碼中找到如下這行程式碼:
假如上一步中,我們從快取中讀取到了單例bean, 本小節將延續這條路線。
前邊講 FactoryBean 的時候就提到它了.
這裡進去做的事就比較好理解了:
1 判斷 bean 型別:如果不是 FactoryBean,或者bean命名不符合 FactoryBean 的命名格式,方法返回
2 若是 FactoryBean,先嚐試從快取獲取( 快取中儲存的是:FactoryBean.getObject() 生成好的bean );從快取獲取失敗後進入後續流程
3 判斷是否單例,如果是單例場景又是一套組合拳:鎖定 singletonObjects 容器,然後進入從FactoryBean呼叫 getObject() 獲取bean;
getObject() 後再次嘗試讀取快取,若獲取到了則捨棄上述讀取動作的新的bean,以快取中的為舊bean作為最終結果;而後進入bean的後處理器的增強操作流程;
而後方法返回前將載入結果放到單例快取容器: factoryBeanObjectCache 中。需要注意的是:容器 factoryBeanObjectCache 僅僅為單例場景服務,因為 "多例" 場景下根本不需要快取,每次直接從 FactoryBean 獲取全新的 bean即可。
【其實這裡也回答了,介紹 FactoryBean那篇文章中末尾遺留的問題:我們並不需要在 FactoryBean 的內部實現單例,spring 容器已經幫我們實現了,FactoryBean的單例管理。 】
4 若不是單例,無需考慮全域性唯一,直接從 FactoryBeab 獲取全新 bean;接著判斷是否需要後置處理器的增強.... 至此流程結束。。。。。
實際上到目前位置的邏輯處理的都是,從快取中成功獲取到了bean的流程。
後續章節中,將圍繞從 0 開始建立bean 的流程展開。
這裡邏輯其實挺簡單的:
1 標記的第一處:其實就是判斷 bean 是否已經進入建立流程,isPrototypeCurrentlyInCreation 內部其實就是一個 ThreadLocal 容器,如果你想深入追蹤它,
你可以關注這個 ThreadLocal 容器的set() 方法的呼叫鏈路,其實不難發現它的set 方法的呼叫,同樣是在 doGetBean() 的後續環節中。
這裡如果我們討論的是一個全新bean的建立,那麼肯定就不存在衝突。
2 標記的第二處: 嘗試獲取當前 BeanFactory 的 "父工廠", 然後判斷當前 BeanFactory 的 BeanDefinitionRegistry 是否有被請求bean 的定義;
如果當前BeanFactory 中不包含該 bean的定義,且 "父工廠" 不為空時,bean 的載入動作將被轉嫁給 "父工廠" 去執行。
很明顯,這裡用 beanName 呼叫了 parentBeanFactory 的 getBean() 、 doGetBean() 等方法,這裡就可以視作套娃遞迴了。
當然如果符合上述的情形,我們當前的分析就到頭。
如果時不走 「父工廠」 的情形,我們接著往下看。
BeanDefinition: 如果你瞭解過 spring 對 xml 檔案的載入,我想你對它不會陌生吧。實際上 spring 對xml 檔案解析的結果,就是以 BeanDefinition 的形式進行儲存的。
如果你還沒忘記,我們當前仍然在 XmlBeanFactory().getBean() 的流程裡。我們的大前提是:通過前邊章節的介紹,XmlBeanFactory 已經完成了 對 xml 組態檔的解析,並通過 BeanDefinitionRegistry 介面進行管理。
如果忘了,我們再把 XmlBeanFactory 的類圖放出來瞅瞅:
接著看原始碼:
這幾行程式碼也比較幹練,這裡做的幾件事無非就是根據 beanName,從BeanDefinitionRegistry 介面讀取, 相應的 BeanDefinition,並進行一定的轉化:
如果當前bean 依賴了別的bean,那麼沒什麼好說的,這裡就轉向被依賴bean的載入。
如果被依賴bean全部被載入完了,或者它就不依賴任何的其它 bean,那麼我們將進入接下來的流程。
如下的縮圖所示,就是本文剩下的全部內容了,這裡分為對:單例bean、原型(「多例」)bean、其它bean的載入,實際上我們接觸最多的就是單例bean的載入。
其實這裡雖然根據不同作用域分為了3種情形,實際進行bean載入的動作其實大同小異,主要的差異在體現在作用域的特性上:
本文篇幅將會拉長,故通過單獨的文章呈現。
從零開始的 多例bean 建立沒啥好說的,因為每次必定建立全新的bean,那麼就不需要設定快取、全域性唯一性檢查等操作了,
所以多例 bean的 建立邏輯反而就只剩下了:
看這個程式碼結構,是不是很眼熟?跟單例bean的建立程式碼大同小異。
那麼我們可以推測,當需要使用其它作用域時,那麼需要向 BeanFactory 註冊相關 "作用域的解析器",也是上述截圖中的:
對應作用域解析器,根據作用特性開發滿足條件的 Scope.get(String beanName, ObjectFactory objFactory) 即可。
比如我們前邊提到的單例作用域bean,就一個目的: 全域性唯一。
如果忘了 bean 還有哪些作用域,可以回頭去複習一下。
這裡也很簡單,getBean 支援指定 bean的型別,當bean 的載入結束後,如果引數指定了: requiredType
那麼就需要進行引數型別校驗。
這裡,除了單純的bean範例化、初始化,還包含了很多其它的的知識:
spring bean 的作用域及其特性,以及spring 面臨相關作用域bean的載入時所做的動作;
FactoryBean 和 ObjectFactory
迴圈依賴
後置處理器
... 等等
別看僅一個 getBean() , 包含的東西實在太多太多了。
實際上到了這裡我們對 spring 基礎功能的學習還是有所欠缺,比如大名鼎鼎的:AOP
畢竟 IOC 和 AOP 是 spring 的兩大基本特性麼。
不過好訊息是對於AOP 的學習可以繼承到目前為止我們的學習成果。