iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0
Software Development

30 篇淺談 Spring 框架的核心底層組件 系列 第 4

Day 04: BeanFactory—— Bean 的創建週期和取得方式

  • 分享至 

  • xImage
  •  

今天來看 Spring 是怎麼把 BD 轉換成 Bean 物件的。

BeanFactory 顧名思義,是負責取得 Bean 的工廠組件。在定義上,BeanFactory 負責其中一項 IoC 容器的五項主要功能;在分類上,它也是所有 IoC 容器直接繼承的基本介面;而在實作上,我們一般寫到的業務邏輯(例如 @Component, @Aspect, @PostConstruct ...等)最後都會導向 BeanFactory 的相關處理。可以說它是整個 Spring 框架裡,最核心圈的重要組件。

BeanFactory 的功能很多,但因為篇幅的原因,我們今天只聚焦在 Default-Listable-Bean-Factory 和宏觀的啟動流程這兩件事情上。Default-Listable-Bean-Factory(後面簡稱 Default 工廠)是主流開發中唯一會使用到的實體工廠。在功能上,它可以做到 BD 的註冊、取得(或創建)Bean、批量查詢 Bean、和處理依賴注入 ...等功能。而在繼承鏈上,Default-Listable-Bean-Factory 有兩個比較需要提及的介面,分別提供了 getBean 和 createBean 這兩個方法。

https://ithelp.ithome.com.tw/upload/images/20260918/2018411464A1ntkdoH.png

getBean

第一個是標題的 BeanFactory 介面。介面內主要定義「如何取得 Bean」和「如何取得 Bean 的其他資訊」的相關功能,因此可以從這個介面找到 getBean 方法。方法的具體實作出現在 Abstract-Bean-Factory(後面簡稱 Abstract 工廠),它是所有工廠實體的共同父類。且跟 Abstract BD 有點類似,Abstract 工廠的用途就是對子類的共用方法進行統一實作。

因為 Default 工廠沒有改動 Abstract 工廠的 getBean 作法,所以我們可以從 Abstract 工廠宏觀地看出一個 Bean 是怎麼在 IoC 容器裡面被找到的。粗略地說:Abstract 工廠的 getBean 會呼叫內部方法 deGetBean,後者又可以再被切分成 8 個不同的子步驟。以流程圖來呈現的話,大概是像下面的這個樣子:

https://ithelp.ithome.com.tw/upload/images/20260918/20184114jvtuuvCFPE.png

步驟 1 的 transformedBeanName 用來將輸入的參數(String name)轉換成真正的 bean name:外部的 name 輸入可以是很多形式,除了最普通的 Bean name 以外,Bean 的別名、FactoryBean 的名稱 ...等,在這裡也是合法的輸入。這些輸入會在第一個步驟裡,被正確收斂成要查找的那個 Bean 名稱。

步驟 2 可以理解成普通的 get 方法,透過查找 IoC 容器中的 Bean Map(不是昨天提到的 Registry 註冊表),如果能找到指定名稱的 Bean,這邊就會回傳該物件;但如果這裡沒有辦法找到,就會回傳 null,並進行後續的相關作業。getSingleton 內部會呼叫一個 Default-Singleton-Bean-Registry 的 getSingleton 方法,這個有點重要,會在明天的文章再詳細說明。

步驟 3 是從父工廠中找尋指定的 Bean 物件。這在早期雙容器的 Spring MVC 架構下可以起到一定的作用(以前的設計是 IoC 組件和 MVC 組件會切割一個明確的上下邊界,MVC layer 的組件可以感知 IoC layer 的組件,但反過來做不到),但現今以 Spring boot 作為主流的開發場景下,通常不會有特別的作用,因為移除了雙容器的設計,改成了單一容器實例,當然就沒有雙工廠了。

後續就是正式的創建流程:步驟 4 是創建的前置作業,會先將當前 Bean 的 BD 標記為「創建中」,鎖定 BD 裡面的定義細節,防止系統在創建的途中進行修改;步驟 5 是 BD 的統一轉換。這裡就是 Day 2 提到「將不同的 BD 實例調整為 Root BD」的真實位置;步驟 6 會簡單檢查 Bean 的創建順序有沒有對齊業務邏輯;步驟 7 就是真正的創建細節了,下一大段會講;步驟 8 則是回傳創建的最終結果。

createBean

Default 工廠另一個需要提及的介面就是 Autowire-Capable-Bean-Factory。它是 Bean-Factory 的子介面,在「取得 Bean」的基礎上,延伸了創建 Bean、依賴注入、和套用 BPP(Bean-Post-Processor)...等 3 大功能。此外,這三個功能也剛好是接下來幾天要說到的內容,但今天我們就先聚焦在創建 Bean 上面。

創建 Bean 的方法就是大標寫到的 createBean。具體定義在 Abstract-Autowire-CapableBean-Factory 上:createBean 是個方法多載,內部呼叫了 doCreateBean。整個 doCreateBean 的流程細節比 doGetBean 再多一點點,共可以分成 11 個子步驟,涵蓋了 Bean 創建週期當中的實例化、賦值、和初始化這 3 個細節。如果整個 Spring 架構裡面最後只能選擇 3 個需要記憶的東西,doCreateBean 的流程細節應該會是我撿起來的其中一個:

https://ithelp.ithome.com.tw/upload/images/20260918/20184114mqFwXn1o6f.png

步驟 1 是物件的實例化,對應 Bean 創建週期的實例化階段。這裡簡單來說就是 new 一個空殼物件出來。唯一要記得的細節是:如果建構子裡帶有其他的 Bean 物件,當前的 doCreateBean 就會暫停,並遞迴呼叫其他物件的 getBean 方法。也因為這個原因,解釋了「為什麼建構子注入的 bean 如果遇到循環依賴,沒加 @Lazy 就會死掉」:因為底層是靠遞迴建構整個流程的,如果在這裡觸就發循環依賴,系統是沒有額外解法的。

步驟 2-4 是實例化之後的內部細節,主要涵蓋了注入 metadata 的快取、實例化攔截、循環依賴、與 AOP 提前暴露的相關機制。也因為這裡會討論到其他核心組件的相關知識,如果現在就展開說明,會花去太多篇幅,所以這邊先直接跳過。會在後續的篇章裡慢慢把這裡的步驟 2-4 撿起來。到時候再回頭看這整個流程會比較有感覺。

步驟 5-6 是 Bean 創建週期的賦值階段,主要處理 @Autowired、@Value 等註解成員變數的賦值,如果一個 Bean 物件使用到其他的 Bean 依賴,這邊就會遞迴呼叫依賴的 getBean 方法取得實例。且不同於步驟 1 的建構子注入,如果循環依賴是以 @Autowired 的方式實現,Spring 可以透過內部的「三層快取」機制處理循環。這個就是明天會提的內容。

步驟 7-9 則是創建週期裡初始化的相關操作。我們可以簡單把初始化理解為「執行那些 Bean 創建完成之後,才可以做的事情。」一般比較常見的用途是執行 @PostConstruct、執行 Bean 的後置處理、或執行 AOP ...等。這幾個東西同樣也是後面會再細部展開,先有一個印象就好。

步驟 10-11 則是收尾。這裡主要做一些 AOP 的代理物件替換、循環依賴的防呆校驗、和註冊銷毀相關的業務邏輯 ...等。在一般的業務場景上可以先不用關注,粗略知道這裡有些整理、防呆、或銷毀相關的邏輯即可。同時處理完這些收尾的內容之後,doCreateBean 就會回傳一個創建好的 Bean 物件了。

最後,細心的朋友可以發現到:因為在 doGetBean 的步驟 7 會嘗試呼叫一次 createBean,而 doCreateBean 內部的步驟 5 又會回頭呼叫 getBean。假設 A/B 相互依賴,create A 的過程就需要 get B,但如果 B 還沒創建,就會遞迴先 create B。而 B 內部如果又依賴 A 的話,容器就會 get 不到 A 回頭嘗試 create A ...。明天我們就來說說,這個著名的依賴問題要怎麼解決。

期待後續各位的閱讀與分享,我是 Pax,我們明天見。


上一篇
Day 03: BeanDefinitionRegistry —— BeanDefinition 的存儲與取得方式
下一篇
Day 05: DefaultSingletonBeanRegistry —— 三層快取的循環依賴解決方法
系列文
30 篇淺談 Spring 框架的核心底層組件 8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言