前面幾天我們花了一些篇幅介紹完了 IoC 容器的核心組件,簡單的介紹了 Reousrce-Loader, Environment, Bean-Factory, Message-Source 和 Application-Publish-Event 等 5 項功能。從今天開始,我們同樣把視野著墨在 Spring 框架上,講講容器以外的其他組件。先從 Spring 框架裡面的另一個 Factory(或者說另一種 Bean?)開始。
FactoryBean 是 Spring 框架的一個核心介面。從名稱聯想,FactoryBean 就是「工廠 Bean」。準確一點說,我們可以把 FactoryBean 想像成「製造 Bean 的特殊 Bean」。介面內有定義 3 個方法:getObject 用來取得 FactoryBean 製造出來的產品;getObjectType 負責快速響應產品的 Bean 是什麼類別;isSingleton 則決定這個 FactoryBean 生產出來的 product Bean 是否為一個 singleton Bean。
這邊可以解釋一下為什麼需要 getObjectType 這個方法:實務上,FactoryBean 通常用來處理建造成本高、流程繁瑣、需要動態代理、或需要建立外部連線的複雜物件。例如 MyBatis 裡面的 SqlSession-Factory-Bean,它負責建立 MyBatis 的其中一個核心組件,過程需要解析專案中的 mapper.xml、綁定 Data-Source、建構 SQL AST 語法樹;或是與 AOP 有關的 Proxy-Factory-Bean,負責建立帶有橫切邏輯的動態代理物件,並依據目標是否實作介面動態抉擇使用 JDK 動態代理或 CGLIB 代理 ...等。
如果沒有 getObjectType 方法,那 Bean 嘗試使用 @Autowired 注入其他組件時,容器為了確認 Factory-Bean 的產品是否符合注入條件,就要提早把產品全做出來,導致啟動效能暴跌、引發更多創建週期跟循環依賴的相關問題。也是因為這樣,Spring 才會額外定義一個 getObjectType,專門解耦「型別宣告」跟「物件實例化」 —— 如果容器需要進行依賴比對,只要呼叫 getObjectType() 就好,不用把產品做完。
再來,可以解釋一下 Factory-Bean 的創建週期,這裡要分成 2 個角度討論:扣掉「可以創建一個額外的 Bean」這個功能以外,Factory-Bean 因為也算是一個系統裡面的合法 Bean,所以它本身的創建週期是遵循組件掃描、實例化、賦值、初始化四步驟的,這裡就不多加解釋直接跳過;產品的創建週期(也就是 getObject() 的調用時機)一般是遵循 lazy-loading 的機制:預設只會記錄在 BD Map,只有在第一次調用到指定的產品,Spring 才會呼叫 getObject() 方法,完成產品的創建。
此外,因為 Factory-Bean 的產品並不走正常 bean 的創建四步驟(準確來說:Factory-Bean 會在 getObject 裡面完成產品的創建邏輯,同時在取得產品後,執行原生 BPP 的 after initialization 方法)。因此無論是 @Autowired, @Value 之類的參數注入、或者是 @PostConstruct 之類的調用方法,都不能作用在產品上面。這點也需要特別注意。
接著來說說 "&" 前綴:因為 FactoryBean 的主要職責就是創建額外的 Bean,且在大多數的場景下,比起去拿工廠,我們更常拿工廠做出來的產品,因此 Spring 在 doGetBean 的流程(Step 8: get-Object-For-Bean-Instance)有一個特別的判斷:如果傳進 doGetBean 的 beanName 參數是一個 Factory-Bean,且 name 上沒有加特殊前綴 "&"。Spring 就會將工廠的產品的結果作為回傳。但如果我們真的想要取得工廠本身,只要在 beanName 之前加上一個 "&" 前綴,就會取消「回傳產品」的動作,改為回傳工廠。
最後,我們來講講作用域:FactoryBean 的 isSingleton 負責決定工廠的產品是否是一個單例 Bean。如果 isSingleton 回傳 true,在 Spring 第一次呼叫 Factory-Bean 的 getObject 之後,就會把產品快取下來,達到 singleton 的效果。但這裡有個小細節:快取的前提是 FactoryBean 本身必須以單例的身分註冊在容器裡面(也就是 FactoryBean 和 getObject 兩個都要是 singleton)。這代表 FactoryBean 如果不是單例,getObject 的產品就有可能是不唯一的。
如果 FactoryBean 不是單例,即是 isSingleton 為 true,產品還是有可能 non-singleton。這時候的產品就沒有辦法單純依賴工廠自身(或其他的 Spring 組件)判斷產品是否唯一了:如果實現細節裡面是 new new new,那產品就不會唯一,但如果 getObject 有依賴外部的單例物件,那即使工廠每次都會重建,取出來的產品仍會是同一個實例。
期待後續各位的閱讀與分享,我是 Pax,我們明天見。