iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
Software Development

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

Day 03: BeanDefinitionRegistry —— BeanDefinition 的存儲與取得方式

  • 分享至 

  • xImage
  •  

在 Spring 的框架裡面,如果我們想要將一份 Java 檔案轉變成 Spring 裡一筆可被使用的 BD 元資料,以宏觀的角度來看,我們大抵需要 3 個 Spring 組件的分工與協作。除了標題寫到的 Bean-Definition-Registry(後續簡稱 Registry)以外,還會需要 Annotated-Bean-Definition-Reader(後續簡稱 Reader)和 Class-Path-Bean-Definition-Scanner(後續簡稱 Scanner)。

AnnotatedBeanDefinitionReader

Reader 是用來「將一個類別主動地轉換成 BD」的 Spring 組件。具體來說,Reader 內部定義了一系列的 registerBean 方法多型,同時將 Class<?> 作為參數。使調用方能透過這個入口,直接將指定的類別註冊進 Spring 容器,使其成為被容器管裡的 BD 之一。

Reader 在這裡的功能相對單純:當我們使用 SpringApplication.run() 啟動整個系統,Spring 首先會建立一個空白的容器,接著 Reader 就會被核心程式調用,將我們的啟動類別打包成一個 BD,同時登記到 Spring 的空白容器當中。而當 Reader 將主類別註冊為 BD 之後,後續的解析組件就能以所在的 package 作為基準路徑掃描,找到 package 底下的所有 Bean。

ClassPathBeanDefinitionScanner

Scanner 是 BD 的掃描器。它主要負責「掃描檔案」和「將檔案轉變為 BD」這兩份工作。跟主動調用的 Reader 不同,Scanner 內部的驅動方法是被動式的 doScan。該方法接收一個(或多個)base-packages 作為參數,接著在拿到這些 packages 之後,就會遍歷 package 底下的所有檔案,並將符合 BD 條件的類別轉換為可被容器管理的 BD。

承襲上一段說的,在 Reader 將主類別註冊成 BD 之後,核心程式就會調用 Scanner,並將主類別的 package 作為參數交給 Scanner,使 Scanner 可以正常檢索核心包底下的所有業務邏輯檔案,並將符合 BD 條件的相關檔案(像是帶有 @Component ...等註解的檔案)整理成 BD。這就是「為什麼我們一般寫的業務程式,都要放在跟 main 那包同級、或 main 那包其他資料夾底下」的主要原因。

這裡可以多提一個小東西:為了處理大型專案可能有上百(甚至上千個)檔案的情況,Scanner 並不會使用傳統的 loadClass 方法進行緩慢的反射載入。Scanner 會借助一個叫做 MetadataReader 的組件,透過更加高效的 ASM 技術檢索檔案 bytecode。這樣除了有速度上的提升以外,因為沒有使用到 loadClass 的反射調用,也可以避免提前觸發類別的靜態初始化區塊,導致邏輯上的相關錯誤。

BeanDefinitionRegistry

當 Reader 設定好 main Class 的主路徑、且 Scanner 透過 main Class 開始進行 base-packages 的掃描之後,就輪到最後的 Registry 登場了。Registry 負責的是 BD 的紀錄與保存,在現代的環境下,通常 IoC 容器會直接充當 Registry 這個角色(IoC 容器除了充當 Registry 以外,還會充當以後介紹到的其他東西)。容器內部有一個簡單的 Map 表,基本上就是負責用來存儲 BD(和 BD 名稱)映射結構。

細節一點來說,Registry 的創建時間其實比 Reader 還要來得早。還記得我們在 Reader 那段說的嗎?「當我們使用 SpringApplication.run() 啟動整個系統,Spring 首先會建立一個空白的容器。」在說這句話的同時,Resistry(其實就是容器本身)就已經被建立起來了。這也是為什麼 Reader 後續可以把啟動類別打包成 BD,並註冊到容器裡面的完整原因。

這裡也可以多提及一個東西:在 Spring 裡面,BD Map 跟 Bean Map 是兩個不一樣的資料結構。清楚一點的說,因為 Prototype Bean、@ConditionOnXxx 或系統相關邏輯的緣故,在 BD Map 裡面的 BD 不一定會全數被轉換成 Bean 物件。如果我們只用一個 Map 同時記錄 BD 和 Bean,是很容易在後續的執行期間發生問題的,因此將「靜態規格」與「最終成品」進行拆分,才是 Spring 能保持系統穩固(和高可讀性)的一種作法。

XmlBeanDefinitionReader

最後補充一個小東西:XML Reader 是 Spring 最早時期用來處理 XML 設定檔的組件,在功能跟定位上比較像是現今 Reader 的早期版本,可以藉由讀取(並解析)中的配置資訊,將裡面的標籤轉換成 BD 並註冊進容器。但 XML 設定檔但跟 Reader 不同的地方在於:XML Reader 接收的參數是 Resource,對應的資料是 classpath 或檔案系統上的 XML 檔案路徑。但因為現在幾乎沒在使用,加上我自己也沒有太多用它的經驗,單純在查找資料的過程中,這個組件一直有被寫出來,所以就簡單提了一下。


簡單來說:Spring 在啟動的環節裡,大致是遵循以下流程找到 BD 的

  1. 初始化 IoC 容器,同時初始化 Bean-Definition-Registry
  2. 呼叫 Annotated-Bean-Definition-Reader,將主類別放進 BD 註冊表
  3. 呼叫 Class-Path-Bean-Definition-Scanner,掃描包路徑下的所有檔案
  4. 將符合 Bean 條件的相關檔案整理成 BD

到這裡為止,大概就是今天要講的所有內容了。今天我們解答了 Spring 是怎麼掃描和儲存 BD,明天我們就來解決昨天提到的第二個問題(Spring 在拿到 BD 之後又會怎麼做)。介紹一下 Spring 當中的 BeanFactory,看看框架裡面的 Bean 工廠是怎麼把 Bean 的定義檔逐步轉換成真正可使用的 Bean 物件的。

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


上一篇
Day 2. BeanDefinition —— 生成 Bean 之前的準備工作
下一篇
Day 04: BeanFactory—— Bean 的創建週期和取得方式
系列文
30 篇淺談 Spring 框架的核心底層組件 8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言