Spring IoC 容器的設計與實現原理
上一篇文章講解的是IOC的原理,這一篇文章主要講解Spring IoC 容器的設計與實現原理
1.spring的IOC容器
在 Spring IoC 容器的設計中,容器有兩個系列,可以看成是容器的具體表現形式:
-
BeanFactory 簡單容器:實現了容器的基本功能,典型方法如 getBean、containsBean、isSingleton;
-
ApplicationContext 應用上下文:在簡單容器的基礎上,增加上下文的特性。
解讀:
為什麼要設計兩個系列,而不是一個?這就涉及到架構設計的模式了,底層定義核心流程,上層擴充套件功能實現,高內聚、低耦合。在架構設計中,這樣的分層是很有必要的,可以隨時替換掉一個抽象層。
Spring 通過定義 BeanDefinition 來管理基於 Spring 的應用中的各種物件以及它們之間的相互依賴關係。BeanDefinition 抽象了我們對 Bean 的定義,是讓容器起作用的主要資料型別。IoC容器是用來管理物件依賴關係的,BeanDefinition 就是對依賴反轉模式中管理的物件依賴關係的資料抽象,也是容器實現依賴反轉功能的核心資料結構,依賴反轉功能都是圍繞對這個BeanDefinition的處理來完成的。
解讀:
BeanDefinition 事實上就是 Bean 的定義在執行時的表現。無論是 xml 配置的 Bean,還是註解定義的 Bean,又或者是自定義掃描進來的 Bean,最終都通過 BeanDefinition 來承載。如果有自定義的 xml 標籤,解析後也是生成 BeanDefinition 註冊到 IOC 中。這樣設計,IOC 只需要關心 BeanDefinition 即可,極大增加了擴充套件性和靈活性。當我們 getBean 的時候,如果 Bean 還沒有初始化,容器就會找到 BeanDefinition,然後根據 BeanDefinition 初始化 Bean 及其依賴。
2.springIOC容器的設計
IoC容器的介面設計如圖所示:
BeanFactory 定義了基本的 IoC 容器的規範,包括了 getBean 方法。HierarchicalBeanFactory 介面在繼承了 BeanFactory 後,增加了getParentBeanFactory 方法,使 BeanFactory 具備了雙親IoC容器的管理功能。在接下來的 ConfigurableBeanFactory 中,定義了一些對 BeanFactory 的配置功能,比如通過 setParentBeanFactory 方法設定雙親IoC容器,通過 addBeanPostProcessor 方法配置Bean後置處理器。
解讀:
可以看到 BeanFactory 只定義了基本功能,是一個最核心的容器介面定義。在 BeanFactory 的基礎上 Spring 通過繼承逐層擴充容器的能力。理解如此多的工廠介面就是在理解 Spring 的設計模式,正如前面所說,這裡的繼承關係也是架構分層的體現。通過繼承和擴充,在 ConfigurableBeanFactory 中基本完成了 BeanFactory 系列的介面定義。當然了,介面分層後,BeanFactory 的每一層具體實現也是分層的,後面會具體解讀。這裡可以看到面向介面開發極大提高了擴充套件性和靈活性。
以 ApplicationContext 為核心的介面系列中,ListableBeanFactory 和HierarchicalBeanFactory 兩個介面連線了 BeanFactory 介面定義和ApplicationConext 應用上下文的介面定義。
在 ListableBeanFactory 介面中,細化了許多 BeanFactory 的介面功能,比如定義了getBeanDefinitionNames 介面方法。對於 ApplicationContext 介面,它通過繼承MessageSource、ResourceLoader、ApplicationEventPublisher 介面,在BeanFactory 的基礎上添加了對高階容器特性的支援。
解讀:
ApplicationContext 繼承了 BeanFactory 介面,具有了容器的基本功能,同時根據上下文的特點,也用 ListableBeanFactory 介面做了功能擴充套件。上下文與容器的主要區別,還是體現在容器高階特性上,比如 MessageSource 實現了國際化、ResourceLoader 實現了資源載入、ApplicationEventPublisher 實現了事件機制。因此平時工作中使用上下文會多一點,一般不直接使用 BeanFactory 簡單容器。
3.FactoryBean
在 BeanFactory 中,Bean 是通過 FactoryBean 來獲取的。FactoryBean是一個工廠Bean,可以生成某一個型別 Bean 的例項,它最大的一個作用是:可以讓我們自定義 Bean 的建立過程。可以使用轉義符 “&” 得到 FactoryBean 本身,用來區分通過容器來獲取 FactoryBean 產生的物件和獲取 FactoryBean 本身。
解讀:
FactoryBean 和 BeanFactory,一個是 Factory,也就是 IOC 容器工廠,一個是特色型別的 Bean。所有的 Bean 都是由 BeanFactory 管理。FactoryBean 是一個能產生或者修飾物件生成的工廠 Bean,它的實現與設計模式中的工廠模式和修飾器模式類似。這兩個型別名稱比較接近,很多人容易混淆,只要記住結尾區分即可,一個是工廠,一個是 Bean。
4.BeanFactory容器的設計原理
BeanFactory 提供了使用 IoC 容器的規範,在這個基礎上,Spring 還提供了符合這個 IoC 容器介面的一系列容器的實現供開發人員使用,以 XmlBeanFactory 的實現為例來說明簡單IoC容器的設計原理。
解讀:
這裡的 XmlBeanFactory 是一個基於 XML 的容器實現。從類圖可以看到,容器的實現也是分層的,每一層介面都有對應的實現,每一個實現都只做自己職責範圍內的事情,通過繼承形成了一個多層次的容器結構。如果我們要定義自己的容器實現,只需要像它一樣按需繼承和實現即可。一定要理解分層的意義,這樣才能設計出更好的實現。
DefaultListableBeanFactory 實際上包含了基本IoC容器所具有的重要功能,在Spring中,實際上是把 DefaultListableBeanFactory 作為一個預設的功能完整的 IoC 容器來使用的。XmlBeanFactory 在繼承了 DefaultListableBeanFactory 容器的功能的同時,增加了新的功能,是一個可以讀取以 XML 檔案方式定義的 BeanDefinition 的IoC容器。
解讀:
在繼承體系中,DefaultListableBeanFactory 實現了容器的重要功能。XmlBeanFactory 解決了 XML 檔案的解析,並把解析出來的 Bean 定義註冊到容器中。這就是一個逐層實現的設計,繼承了預設的實現後,只需要根據自己的場景做定製即可,每一層的實現都不算複雜。
在 XmlBeanFactory 中,初始化了一個 XmlBeanDefinitionReader,用來讀取以XML方式定義的 BeanDefinition。而 XML 作為資原始檔,通過 Resource 類來封裝 I/O 操 作。XmlBeanDefinitionReader 初始化後,呼叫 loadBeanDefinitions 方法從Resource 中載入 BeanDefinition。
解讀:
一個真正完整的容器在啟動階段主要做幾個事情:
-
找到 Bean 定義,如 xml、註解等,如果是資原始檔可以用 Resource 類來封裝,支援 ClassPath、jar、URL 等;
-
初始化 Reader 注入 Resource,BeanDefinitionReader 介面定義瞭解析相關的方法,Spring 預設提供了很多實現類;
-
Reader 解析 BeanDefinition,初始化後註冊到容器中。
-
5.ApplicationContext容器的設計原理
以常用的 FileSystemXmlApplicationContext 的實現為例。主要功能已經在AbstractXmlApplicationContext 中實現了,在 FileSystemXmlApplicationContext中,作為一個具體的應用上下文,只需要實現和它自身設計相關的兩個功能。
如果應用直接使用 FileSystemXmlApplicationContext,對於例項化這個應用上下文的支援,同時啟動IoC容器的 refresh() 過程。這個 refresh() 過程會牽涉 IoC 容器啟動的一系列複雜操作,同時,對於不同的容器實現,這些操作都是類似的,因此在基類中將它們封裝好。所以,我們在FileSystemXml的設計中看到的只是一個簡單的呼叫。
解讀:
refresh 是上下文的很重要的一個操作。Spring容器的啟動,初始化一些容器啟動必要的資源,BeanFactory 的建立、初始化,Bean 的建立、初始化、註冊、非懶載入,註冊和設定國際化工具類MessageSource,註冊和設定事件,等一系列過程都在這個 refresh 方法裡面進行呼叫。關於 refresh 的實現原理,後續會有文章解讀,限於篇幅,這裡就不展開了。
FileSystemXmlApplicationContext 是一個從檔案系統載入 XML 的上下文實現,因此
設計了從檔案系統中載入XML的功能。
解讀:
可以看到,Spring 內部上下文的實現和繼承關係非常複雜,難以理解。實際上,按照實現分層的思路去理解還是比較容易的,每一層只實現自己相關的功能。類似或者公用的能力都往下沉澱變為底層的基礎能力,上層實現只做呼叫。看原始碼的時候,要有全域性視野,哪些是公用能力,哪些是本層次定製功能,這樣就會好理解一點。