在Spring IOC容器的設計中,我們可以看到兩個主要的容器系列,一個是實現BeanFactory接口的簡單容器系列,這系列容器只實現了容器的最基本的功能,另一個是ApplicationContext,他作為容器的高級形態而存在,應用上下文在簡單容器的基礎上,增加了許多面向框架的特性,同時對應用環境做了許多適配,有了這兩種基本的容器系列,基本上可以滿足用戶對IOC容器使用的大部分需求了。
Spring IOC 容器系列
IOC容器作為開發者管理對象之間的依賴關系提供了許多便利和基礎服務,有許多IOC容器供開發者選擇,SpringFramework 的IOC 核心就是其中的一個,它是開源的,那具體什么是IOC容器呢?它在Spring框架之中到底長什么樣?其實對IOC容器的使用者來說,我們經常接觸到的BeanFactory和ApplicationContext都可以看成是容器的具體表現形式,如果深入到Spring的實現中去看,我們通常所說的IOC容器,實際上代表着一系列功能各異的容器產品,只是容器的功能有大有小,有各自的特點,以水桶為例,在商店中出售的水桶大小各異,制作材料也各不相同,但只要能裝水,具備水桶的基本特性,那么就可以作為水桶出售,來讓用戶使用,是用什么樣的水桶完全取決於用戶的需要,但在使用之前如果能夠了解容器的基本情況,那對容器的使用是非常有幫助的,就像我們在購買商品之前,對商品進行觀察和挑選那樣,這次我們從代碼的角度入手,可以看到關於這一系列容器的設計情況,
就像商品需要有產品基本規格一樣,同樣作為IOC容器,也需要為它的具體實現指定基本的功能規范,這個功能的基本規范的設計表現為接口類的BeanFactory,它體現了Spring為用戶使用的IOC容器所設定的最基本的規范,還是以前面的水桶為例,如果把IOC容器看作為水桶,那么這個BeanFactory就定義了可以作為水桶的基本功能,比如至少能裝水,有個提手等,除了滿足基本的功能,為了不同場合的需要,水桶的生產者還需要提供更豐富的功能,有簡約型的,有豪華型的、等,但是不管是什么水桶,它都需要一項最基本的功能,能裝水,那對Spring的具體IOC容器實現來說,他需要滿足什么樣的基本特性呢?它需要滿足BeanFactory這個基本接口的定義,它是作為一個最基本的接口類出現在Spring IOC容器系列中的,
在這些 Spring提供的基本IOC容器的接口定義和實現基礎之上,Spring通過定義BeanDefinition來管理基於Spring的應用中的各種對象以及它們之間相互依賴關系,BeanDefinition抽象了我們對於Bean的定義,是讓容器起作用的主要數據類型,我們都知道 在計算機世界類,所有的功能都是建立在通過 現實數據進行抽象的基礎上的,IOC容器是用來管理對象依賴關系的,對IOC容器來說,BeanDefinition就是對依賴反轉模式中管理的對象依賴關系的數據抽象,也是容器實現依賴反轉功能的核心數據結構,依賴反轉功能都是圍繞着對這個BeanDefinition的處理來完成的,這些BeanDefinition就像容器里裝的水,有了這些基本數據,容器才能發揮作用,
同時在使用IOC容器時,連接BeanFactory和ApplicationContext之間的區別對我們理解和使用IOC容器也是比較重要的,弄清楚這兩種重要容器之間的 區別和聯系,意味着我們具備了辨別容器系列中不同容器產品的能力,還有一個好處就是,如果需要定制特定功能的容器實現,也能比較方便的在容器系列中找到一款恰當的產品作為參考,不需要重新設計。
Spring IOC容器的設計
- 從接口BeanFactory到HierarchicalBeanFactory,再到ConfigurableBeanFactory是一條主要的BeanFactory設計路徑,在這條接口設計路徑中,BeanFactory接口定義了基本的IOC容器的規范,在這個接口定義中,包括了getBean這樣的IOC容器的基本方法,而HierarchicalBeanFactory接口在繼承了BeanFactory基本接口之后,增加了getParentBeanFactory的接口功能,使BeanFactory具備了雙親IOC容器的管理功能,在接下來的ConfigurableBeanFactory中,主要定義了一些對BeanFactory的配置功能,比如通過setParentBeanFactory設置雙親IOC容器,通過addBeanPostProcessor配置Bean的后置處理器,等等,通過這些接口的疊加,定義的BeanFactory就實現了簡單的IOC容器的基本功能
- 第二條設計的主線是:以ApplicationContext應用上下文為核心的接口設計,這里主要涉及的接口設計有:從BeanFactory到ListableBeanFactory,再到ApplicationContext,在到我們常用的WebApplicationContext或者ConfigurableBeanFactory接口,我們常用的應用上下文基本上都是ConfigurableBeanFactory或者 WebApplicationContext的實現,在這個接口體系中,ListableBeanFactory和HierarchicalBeanFactory兩個接口,連接BeanFactory接口定義和ApplicationContext應用上下文的接口定義,在ListableBeanFactory中,細化了許多BeanFactory的接口功能,比如定義了getBeanDeifinitionNames接口方法。對於HierarchicalBeanFactory上面已經提到過了,這兩個接口其實仔細看 是屬於一類接口,純屬個人看法,對於ApplicationContext接口,它通過集成MessageSource、ResourceLoader、ApplicationEventPublisher接口,在BeanFactory簡單IOC容器基礎上添加了許多對高級容器的特定的支持
- 上面涉及到的是主要接口關系,而具體的IOC容器都是在這個接口體系下實現的,比如DefaultListableBeanFactory,這個基本IOC容器的實現就是 實現了ConfigurableBeanFactory,從而成為一個簡單IOC容器的實現,像其他IOC容器,比如XMLBeanFactory,都是在DefaultListableBeanFactory的基礎上 做擴展,同樣的ApplicationContext的實現也是如此。
- 這個接口系統是以BeanFactory和ApplicationContext為核心的,而BeanFactory又是IOC容器的最基本接口,在ApplicationContext的設計中,可以看到它繼承了BeanFactory接口體系中的ListableBeanFactory、AutowireCapableBeanFactory、HierarchicalBeanFactory等BeanFactory的接口,具備了BeanFactory IOC容器的基本功能,另一方面,通過繼承了MessageSource、ResourceLoader、ApplicationEventPublisher這些接口,ApplicationContext為BeanFactory賦予了更高級的IOC容器特性,對於ApplicationContext而言,為了在Web環境中使用它,還設計了WebApplicationContext接口,而這個接口通過集成ThemeSource來擴展功能。
-
BeanFactory定義的基本規范
BeanFactory提供的是最基本的IOC容器的功能,關於這些功能定義,我們可以在接口BeanFactory中看到,
BeanFactory接口定義了IOC容器最基本的形式,並且提供了IOC容器應該遵守的最基本的服務契約,同時,這也是我們使用IOC容器所應該遵守的最低層 和 最基本的編程規范,這些接口定義勾畫出了IOC基本輪廓,很顯然,在Spring的代碼實現中,BeanFactory只是一個接口類,並沒有給出容器的具體實現,而我們看到的各種具體類,比如DefalutListableBeanFactory、XMLBeanFactory、ApplicationContext等都可以看成是容器或者是BeanFactory附加了某種功能的具體實現,也就是容器體系中華的具體容器產品,下面我們來看看BeanFactory是怎么樣定義IOC容器的基本接口的,
public interface BeanFactory { /** * 用戶使用容器時,可以使用轉義符“&”來得到FactoryBean本身 */ String FACTORY_BEAN_PREFIX = "&"; /** * 通過制定名字來取得IOC容器中管理的Bean */ Object getBean(String name) throws BeansException; /** * 通過指定名字來取得IOC容器中管理的Bean,必須是指定類型的Bean */ <T> T getBean(String name, Class<T> requiredType) throws BeansException; /** * 通過指定獲取Bean的類型 來獲取Bean */ <T> T getBean(Class<T> requiredType) throws BeansException; /** * 通過獲取Bean的名字,還有指定的構造參數來獲取Bean */ Object getBean(String name, Object... args) throws BeansException; /** * 在上一個方法基礎上急了 類型檢查 */ <T> T getBean(Class<T> requiredType, Object... args) throws BeansException; /** * 讓用戶能夠判斷容器是否含有指定名字的Bean */ boolean containsBean(String name); /** * 查詢指定名字的Bean是否是Singleton類型的Bean,對於Singleton屬性, 用戶可以在BeanDefinition中指定 */ boolean isSingleton(String name) throws NoSuchBeanDefinitionException; /** * 查詢指定名字的Bean是否是Prototype類型的,與Singleton屬性一樣也可以在BeanDefinition中指定 */ boolean isPrototype(String name) throws NoSuchBeanDefinitionException; /** 查詢指定名字的Bean的Class類型 是否是特定的Class類型,這個Class類型通過第二個參數來指定 */ boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException; /** *來查詢指定名字的Bean的Class類型 */ Class<?> getType(String name) throws NoSuchBeanDefinitionException; /** * 查詢指定名字的Bean的所有別名,這些別名是用戶在BeanDefinition中定義的額 */ String[] getAliases(String name); }
可以看到這里定義的只是一系列的接口方法,通過這一系列的BeanFactory接口,可以使用不同的Bean的檢索方法,很方便的從IOC容器中得到需要的Bean,從而忽略具體的IOC容器的實現,從這個角度看的話,這些檢索方法代表的是最為基本的容器入口。
BeanFactory容器的設計原理
BeanFactory接口提供了使用IOC容器的基本規范,在這個基礎上,Spring還提供了符合這個IOC容器接口的一系列容器的實現供開發人員使用,我們以XMLBeanFactory的實現為例,來說明簡單IOC容器的設計原理,下面的圖為XMLBeanFactory設計的累積成關系
可以看到,作為一個簡單IOC容器系列最低層實現的XMLBeanFactory,與我們在Spring應用中用到的那些上下文相比,有一個非常明顯的特點,它只提供最基本的IOC容器的功能,理解這一點有助於我們理解ApplicationContext與基本的BeanFactory之間的區別和聯系,我們可以認為直接的BeanFactory是現實IOC容器的基本形式,而各種ApplicationContext的實現是IOC容器的高級表現形式,關於ApplicationContext的分析,以及他與BeanFactory相比的增強特性都會在下面進行詳細的分析,
從上面的繼承關系可以清楚的看到類之間的聯系,他們都是IOC容器系列的組成部分,在設計這個容器系列時,我們可以從繼承體系的發展看到IOC容器的各項功能的實現過程,如果需要擴展自己的容器產品,那么建議呢先看看繼承體系中,是不是已經提供了現成的或者相近的容器實現供我們參考,
仔細閱讀XMLBeanFactory的源碼,從一開始的注釋里就會看到對XMLBeanFactory功能的簡要說明,從代碼的注釋還可以看到,這是Rod Johnson大佬在2001年就寫下的代碼,可見這個類應該是Spring的元老類了,XMLBeanFactory繼承自DefaultListableBeanFactory這個類,后者非常重要,我們經常要用到的一個IoC容器的實現,比如在設計應用上下文ApplicationContext時,就會用到它,我們會看到這個DefaultListableBeanFactory實際上包含了基本IOC容器具有的重要功能,也是在很多地方都會用到的容器系列中的一個基本產品。
在Spring中,實際上是把DefaultListableBeanFactory作為一個默認的功能完整的IOC容器來使用的,XmlBeanFactory在繼承了DefaultListableBeanFactory容器的功能同時,增加了新的功能,這些功能很容易從XMLBeanFactory的名字上猜到,它是一個與XML相關的BeanFactory,也就是說它是一個可以讀取XML文件方式定義BeanDefinition的IOC容器。
這些實現XML讀取的功能是怎么樣實現的呢?對這些XMl文件定義信息處理並不是由XMLBeanFactory直接完成的,在XMLBeanFactory中,初始化了一個XMLBeanDefinitionReader對象,有了這個Reader對象,那些以Xml方式定義的BeanDefinition就有了處理的地方,我們可以看到,對這些Xml形式的信息處理實際上是由這個XMLBeanDefinitionReader來完成的。
構造XMLBeanFactory這個IOC容器時,需要制定BeanDefinition的信息來源,而這個信息來源需要封裝成Spring中的Resource類給出,Resource是Spring用來封裝I/O操作的類,比如,我們的BeanDefinition信息是以Xml文件形式存在的,那么可以使用像
ClassPathResource res = new ClassPathResource("beans.xml");
,這樣具體的ClassPathResource來構造需要的Resource,然后將Resource作為構造參數傳遞給XMLBeanFactory構造參數,這樣IOC容器就可以方便的定位到需要的BeanDefinition信息來對Bean完成容器的初始化和依賴注入過程。
XMLBeanFactory的功能是建立在DefaultListableBeanFactory這個基本容器之上的,並在這個基本容器的基礎上實現了植入XML讀取的附加功能,對於這些的功能實現原理,看一看XMLBeanFactory的代碼就很容器的理解,如果下面代碼所示:在XMLBeanFactory的構造方法需要得到Resource對象,對XMLBeanDefinitionReader對象的初始化,以及使用這個對象來完成對loadBeanDefinitions的調用,就是這個調用啟動從Resource中載入BeanDefinitions的過程,LoadBeanDefinitions同時也是IOC容器初始化的重要組成部分。
@Deprecated @SuppressWarnings({"serial", "all"}) public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); /** * Create a new XmlBeanFactory with the given resource, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } /** * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } }
我們看到XMLBeanFactory使用了DefaultListableBeanFactory作為基類,DefaultListableBeanFactory是很重要的一個IOC實現,在其他IOC容器中,比如ApplicationContext,其實現的基本原理和XmlBeanFactory一樣,也是通過持有或者擴展DefaultListableBeanFactory來獲得基本的IOC容器的功能的。
參考XMLBeanFactory的實現,我們以編程的方式使用DefaultListableBeanFactory,從中我們可以看到IOC容器使用的一些基本過程,對我們了解IOC容器的工作原理是非常有幫助的,因為這個編程式使用IOC容器過程,很清楚的揭示了在IOC容器實現中那些關鍵的類,可以看到他們是如何把IOC容器功能解耦的,又是如何結合在一起為IOC容器服務的,
/** * <p>Title:MyBeanFactory.java</p> * <p>Description:純屬學習</p> * <p>Copyright:ChenDong 2019</p> * @author 陳東 * @date 2019年1月8日 * @version 1.0 */ package myPage.learn1.core; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.ClassPathResource; /** * <p> * <b>Title:MyBeanFactory.java</b> * </p> * <p> * Copyright:ChenDong 2019 * </p> * <p> * Company:僅學習時使用 * </p> * <p> * 類功能描述:以編程的方式 來揭示 IOC容器 是如何將 功能解耦 以及 又如何將這些功能進行組合一起為IOC容器服務 * </p> * * @author 陳東 * @date 2019年1月8日 下午8:19:53 * @version 1.0 */ public class MyBeanFactory { public static void main(String[] args) { // 以XmlBeanFactory為例 ,它的構造器必須傳入一個Resource 對象,也就創建IOC配置文件的抽象資源 這個抽象資源包含了 // BeanDefinition的定義信息 ClassPathResource res = new ClassPathResource("beans.xml"); // 創建一個BeanFactory 這里用DefaultListableBeanFactory為例,XmlBeanFactory 繼承了這個類 DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // 創建一個載入 IOC容器配置文件的 讀取器,這里使用XMLBeanFactory中使用的XmlBeanDefinitionReader // 讀取器 來載入XML文件形式的BeanDefinition,通過一個回到配置給BeanFactory XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); // 從定義好的資源位置讀入配置信息,具體的解析過程有XmlBeanDefinitionReader來完成,完成整個載入和注冊Bean定義后 // 需要的IOC容器就建立起來了,這個時候就可以直接使用IOC容器了 reader.loadBeanDefinitions(res); } }
這樣我們就可以通過Factory獨享來使用DefaultListableBeanFactory這個IOC容器了,在使用IOC容器時 需要以下幾個步驟:
- 創建IOC配置文件的抽象資源,這個抽象資源包含了BeanDefinition的定義信息
- 創建一個BeanFactory,這里使用DefaultListableBeanFactory
- 創建一個載入BeanDefinition的讀取器,這里使用XmlBeanDefinitionReader來載入XML文件形式的BeanDefinition,通過一個回調配置給BeanFactory。
- 從定義好的資源位置讀取配置信息,具體的解析過程由 XmlBeanDefinitionReader來完成,完成整個載入和注冊Bean定義后,需要的IOC容器就建立起來了,這個時候就可以使用IOC容器了。
ApplicationContext的應用場景
上面我們了解了IOC容器建立的基本步驟,理解這些步驟之后,可以很方便的通過編程的方式,來手工控制這些配置和容器的建立過程了,但是,在Spring中,系統已經為用戶提供了許多已經定義好的容器實現,而不需要開發人員事必躬親,相比那些簡單擴展BeanFactory的基本IOC容器,開發人員常用的ApplicationContext除了能提供前面介紹的容器的基本功能之外,還為用戶提供了一下的附加服務,可以讓客戶更方便的使用,所以說,ApplicationContext是一個高級形態意義的IOC容器,看下的圖,可以看到ApplicationContext在BeanFactory的基礎上添加的附加功能,這些功能為ApplicationContext提供了一下BeanFactory不具備的新特性:
- 支持不同的信息源,除了上面的類之外, ApplicationContext還擴展了MessageSource接口,這些信息源的擴展功能可以支持國際化的實現,為開發多語言版本的應用提供服務
- 訪問資源,這一特性體現在對ResourceLoader 和 Resource 的支持上,這樣我們可以從不同地方得到Bean的定義資源,這種抽象使 用戶程序可以靈活的定義Bean的定義信息,尤其是從不同的I\O途徑得到Bean的定義信息 也就BeanDefinition信息,這在接口關系上看不出來,不過一般來說,具體的ApplicationContext都是繼承了DefaultResourceLoader的子類,因為DefaultResourceLoader是AbstractApplicationContext的基類。
- 支持應用事件,繼承了接口ApplicationEventPublisher,從而在上下文中引入了事件機制,這些事件機制和Bean的聲明周期的結合為Bean的管理提供了便利。
在ApplicationContext中提供的附加服務,這些服務使得基本IOC容器的功能更加豐富,因為具備了這些豐富的附加功能,使得ApplicationContext與簡單的BeanFactory相比,對它的使用是一種面向框架的使用風格,所以一般建議在開發應用時使用ApplicationContext作為IOC容器的基本形式。
ApplicationContext容器的設計原理
在ApplicationContext容器中,我們以常用的額FileSystemXmlApplicationContext的實現為例來說明ApplicationContext容器的設計原理。
在FileSystemXMLApplicationContext的設計中,我們看到ApplicationContext應用上下文的主要功能已經在FileSystemXMLApplicationContext的基類 AbstractXMLApplicationContext中實現了,在FileSystemXMLApplicationContext中,作為一個具體的應用上下文,只要實現和它自身相關的兩個功能就可以了。
一個功能是,如果應用直接使用FileSystemXMLApplicationContext,對於實例化這個應用上下文的支持 其實就是構造器,同時啟動IOC容器的refresh()過程,這在FileSystemXMLApplicationContext的代碼實現中可以看到,
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
這個refresh過程會牽涉IOC容器啟動的一系列復雜操作,同時,對於不同的容器實現,這些操作都是類似的,因此在基類中將他們封裝好,所以我們在FileSystemXMLApplicationContext的設計中看到的只是一個簡單的調用,關於這個refresh在IOC容器中的具體實現,是后面要分析的主要內容,這個到時候看后面的分析就行了。
另一個功能是 與FileSystemXMLApplicationContext設計具體相關的功能,這部分與怎么樣從文件系統中加載XML的Bean定義資源有關。
通過這個過程,可以為在文件系統中讀取以XML形式存在的BeanDefinition做准備,因為不同的應用上下文實現對應着不同的讀取BeanDefinition形式,在FileSystemXMLApplicationContext的實現如下:
@Override protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
可以看到,這個方法是一個 override方法 也就是 為了實現以不同的方式 獲取資源定位 在某個接口定義的 getResourceByPath方法,調用這個方法可以得到FileSystemResource的資源定位。
IOC容器的初始化過程
簡單來說,IOC容器的初始化時由前面介紹的refresh方法來啟動的,這個方法標志着IOC容器正式啟動,具體來說,這個啟動包括BeanDefinition的Resource定位、載入和注冊三個基本過程, 如果我們了解如何編程式的使用IOC容器,就可以清楚的看到Resource定義和載入過程的接口調用,在下面的內容中,我們將會詳細分析這三個過程的實現。
在分析之前,要提醒大家的是,Spring把這三個過程分開,並使用不同的模塊來完成,如使用相應的ResourceLoader、BeanDefinitionReader等模塊,通過這樣的設計方式,可以讓用戶更加靈活的對這三個過程進行剪裁和擴展,定義出最適合自己的IOC容器的初始化過程。
- 第一個過程:是Resource定位過程,這個Resource定位指的是BeanDefinition的資源定位,它由ResourceLoader通過統一的Resource接口來完成,這個Resource對各種形式的BeanDefinition的使用都提供了統一接口,對於這些BeanDefinition的存在形式,相信大家都不會感到默生,比如,在文件系統中的Bean定義信息可以使用FileSystemResource來進行抽象,在類路徑中的Bean定義信息可以使用前面提到的ClassPathResource來進行抽象,等等,這個定位過程類似於容器中尋找數據的過程,就像是用水桶裝水,先要將水找到一樣。
- 第二過程是:BeanDefinition的載入,這個載入過程是把用戶定義好的Bean表示成IOC容器內部的數據結構,而這個容器內部的數據結構就是BeanDefinition,下面介紹這個數據結構的詳細定義,具體來說,這個BeanDefinition實際就是POJO對象在IOC容器中的抽象,通過這個BeanDefinition定義的數據結構,使IOC容器能夠方便的對POJO對象也就是Bean進行管理,
- 第三個過程就是向IOC容器注冊這個鞋BeanDefinition的過程,這個過程是通過調用BeanDefinitionRegistry接口的實現來完成的,這個注冊過程是把載入過程中解析得到的BeanDefinition向IOC容器進行注冊,通過分析,我們可以看到,在IOC容器內部將BeanDefinition注入到一個HashMap中去,IOC容器就是通過這個HashMap來持有這些BeanDefinition數據的。
這里談的是IOC容器的初始化過程,在這個過程中,一般不包含Bean依賴注入的實現,在Spring IOC的設計中,Bean定義的載入和依賴注入是兩個獨立的過程,依賴注入一般發生在應用第一次通過getBean向容器索取Bean的時候,但是有一個例外,在使用IOC容器時有一個預實例化的配置(具體來說,可以通過為Bean 定義信息中的lazyinit屬性),用戶可以對容器初始化過程做一個微小的控制,從而改變這個被設置了lazyinit屬性的Bean的依賴注入過程,舉例來說,如果我們對某個Bean設置了lazyinit屬性,那么這個Bean的依賴注入在IOC容器初始化的時候就預先完成了,而不需要等到整個初始化完成以后、第一次使用getBean時才會觸發。
那么下面開始詳細分析上面的三個步驟
BeanDefinition的Resource定位
以編程的方式使用DefaultListableBeanFactory時,首先定義一個Resource來定位容器使用的BeanDefinition,這時使用是ClassPathResource,這意味着Spring會在類路徑中去尋找以文件形式存在BeanDefinition。
ClassPathResource resource = new ClassPathResource("beans.xml");
這里的Resource並不能由DefaultListableBeanFactory直接使用,Spring通過BeanDefinitionReader來對這些信息進行處理,在這里,我們也可以看到使用ApplicationContext相對於直接使用DefaultListableBeanFactory的好處,因為在ApplicationContext中,Spring已經為我們提供了一系列加載不同Resource的讀取器實現,而DefaultListableBeanFactory只是一個純粹的IOC容器,需要為它配置配置特定的讀取器才能完成這些功能,當然了 利 和 弊是共存的,使用DefaultListableBeanFactory這樣更底層的IOC容器,能提高定制IOC容器的的靈活性。
回到我們經常使用的ApplicationContext上來,例如FileSystemXMLApplicationContext、ClassPathXMLApplicationContext以及XMLWebApplicationContext等,簡單的從這些類的名字上分析,可以清楚的看到他們可以提供哪些不同的Resource讀入功能,比如FileSystemXMLApplicationContext可以從文件系統中載入Resource,ClassPathXMLApplicationContext可以從 class path載入Resource,XMLWebApplicationContext可以在Web容器中載入Resource等,
下面以FileSystemXMLApplicationContext為例,通過分析這個ApplicationContext的實現來看看它是怎么完成這個Resource定位的,
從源代碼實現的角度,我們可以近距離關心以FileSystemXMLApplicationContext為核心的繼承體系,這個FileSystemXMLApplicationContext已經通過繼承AbstractApplicationContext具備了ResourceLoader讀入以Resource定義的BeanDefinition的能力,因為AbstractApplicationContext的基類是DefaultResourceLoader,下面讓我們看看FileSystemXMLApplicationContext的具體實現:
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext { /** * */ public FileSystemXmlApplicationContext() { } /** * */ public FileSystemXmlApplicationContext(ApplicationContext parent) { super(parent); } /** * 這個構造函數的configLocation包含的是BeanDefinition所在的文件路徑 */ public FileSystemXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); } /** * 這個構造函數 的允許configLocation包含多個BeanDefinition的文件路徑 */ public FileSystemXmlApplicationContext(String... configLocations) throws BeansException { this(configLocations, true, null); } /*這個構造函數 允許configLocation包含多個BeanDefinition的文件路徑,還允許指定自己的雙親容器 */ public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { this(configLocations, true, parent); } /** *這個構造函數 在允許 configLocation包含多個BeanDefinition的文件路徑基礎上,還加了是否初始化容器的標志 */ public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException { this(configLocations, refresh, null); } /** *在對象的初始化過程中,調用refresh函數載入BeanDefinition,這個refresh啟動了BeanDefinition的載入過程 */ public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } /** *這事應用於文件系統中Resource的實現,通過構造一個FileSystemResource來得到一個文件系統中定位的BeanDefinition,這個getResourceByPath是在BeanDefinitionReader的LoaderBeanDefinition中被調用的,loaderBeanDefinition采用了模板模式,
*具體的定位實現實際上是由各個子類來完成的 */ @Override protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); } }
在FileSystemXMLApplicationContext中,我們可以看到在構造函數中,實現了對configuration進行處理的功能,讓所有配置在文件系統中的,以XML文件形式存在的BeanDefinition都能夠得到有效的處理。
實現了getResourceByPath方法,這個方法是一個模板方法,是為讀取Resource服務的,對於IOC容器功能的實現,這里沒有涉及,因為它繼承了AbstractXMLApplicationContext,關於IOC容器功能的相關實現,都是在AbstractXMLApplicationContext中完成的,但是在構造函數中通過refresh來啟動IOC容器的初始化,這個refresh方法非常重要,也是我們以后分析容器初始化過程實現的一個重要入口。
注意: FileSystemXMLApplicationContext是一個支持Xml定義BeanDefinition的ApplicationContext,並且可以指定以文件形式的BeanDefinition的讀入,這些文件可以使用文件路徑和URL定義來表示,在測試環境和獨立應用環境中,這個ApplicationContext是非常有用的。
根據上面的調用關系分析 ,我們可以清楚的看到整個BeanDefinition資源定位的過程,對這個BeanDefinition資源定位的過程,最初是由refresh來觸發的,這個refresh的調用是在FileSystemXMLApplicationContext的構造函數中啟動的,
大致的調用過程是
可能大家看了上面的調用過程會比較好奇,這個FileSystemXMLApplicationContext在什么地方定義了BeanDefinitionReader,從而完成BeanDefinition的讀入呢?在前面分析過,在IOC容器的額初始化過程中,BeanDefinition資源的定位、讀入 和 注冊 過程都是分開進行的,這也是一個解耦的體現,關於這個讀入其的配置,可以到FileSystemXMLApplicationContext的基類AbstractRefreshableApplicationContext中看看它是怎么樣實現的。
我們重點看看AbstractRefreshableApplicationContext的 refreshBeanFactory()方法的實現看下,這個refreshBeanFactory()方法被FileSystemXMLApplicationContext的構造函數refresh調用,在這個方法中通過 createBeanFactory()構建了一個IOC容器供給ApplicationContext使用,這個IOC容器就是之前我們提到過的DefaultListableBeanFactory,同時它啟動了loadBeanDefinitions(beanFactory); 來載入BeanDefinition,這個過程和我們前面以編程的方式來使用IOC容器的過程非常相似。
在FileSystemXMLApplicationContext的源代碼中我們可以看到,在初始化FileSystemXMLApplicationContext的過程中,通過refresh來啟動整個調用,使用的IOC容器是DefaultListableBeanFactory,具體的資源載入在XmlBeanDefinitionReader讀入BeanDefinition時完
成,在XmlBeanDefinitionReader的基類AbstractXmlBeanDefinitionReader中可以看到這個載入過程的具體實現,對載入過程的啟動,可以在AbstractRefreshableApplicationContext的loadBeanDefinitions方法中看到。
我們先從 refresh啟動開始:
具體實現的ApplicationContext也就是當前的FileSystemXMLApplicationContext調用的refresh方法 是其繼承的基類 AbstractApplicationContext中的實現:
先跟蹤第一步 AbStractApplicationContext中的refresh方法
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // 先着重看這個方法 這是初始化容器且 進行BeanDefinition載入的入口 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
跟蹤第二步: obtainFreshBeanFactory()這個方法還是在AbstractApplicationContext中實現:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { //初始化容器 refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
但是 refreshBeanFactory(); 這個具體初始化IOC容器的實現呢 AbstractApplicationContext 沒有做具體實現,而是交給其子類完成,實現了解耦。讓初始化IOC容器變得更加靈活,那么咱們在回顧下FileSystemXMLApplicationContext所繼承的父類的實現,
跟蹤第三步:
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
refreshBeanFactory定義成了abstract 那么也就是繼承它的 子類必須實現的,那么看下FileSystemXMLApplicationContext所處在的繼承體系,那么很顯然我們應該知道去那個類中去找這個方法的實現了。沒錯就是AbstractRefreshableApplicationContext中
@Override protected final void refreshBeanFactory() throws BeansException { //這里判斷,如果已經建立了BeanFactory,則銷毀並關閉該BeanFactory if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } //這里是創建並設置持有的DefaultListableBeanFactory的地方同時調用loadBeanDefinitions載入BeanDefinition信息 try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
我們先看下創建DefaultListableBeanFactory的地方:createBeanFactory,這個方法 是在AbstractRefreshableApplicationContext中實現,所以AbstractApplicationContext 讓我們可以充分自由的實例化自己想初始化的原始IOC容器,
protected DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(getInternalParentBeanFactory()); }
getInternalParentBeanFactory 獲取當前容器已有的父親容器,來作為新容器的父容器,這個方法是在AbstractApplicationContext中實現的。
那么我們在對代碼進行繼續跟蹤 loadBeanDefinitions(beanFactory),這個方法就是BeanDefinitionReader載入 BeanDefinition的地方,因為允許有多種載入方式,雖然用的最多的是XML定義的形式,但是為了面向框架的擴展性,這里AbstractRefreshableApplicationContext將loadBeanDefinitions做成了一個抽象函數把具體的實現委托給其子類實現。
跟蹤第四步
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException;
那么回過頭來,還是看上面FileSystemXMLApplicationContext的類繼承體系,我們可以看到 FileSystemXMLApplicationContext的基類AbstractXmlApplicationContext 是一個AbstractRefreshableApplicationContext的一個子類實現,那我們知道了該怎么去找這個loadBeanDefinitions的具體實現了把。
@Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 這就是初始一個BeanDefinitionReader的地方 當然了這利用的XMLBeanDefinitionReader的實現 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initBeanDefinitionReader(beanDefinitionReader); // 這里開始就是 加載 獲取BeanDefinition資源定位 並且 載入模塊的開始了 loadBeanDefinitions(beanDefinitionReader); }
繼續跟蹤這個 loadBeanDefinitions(beanDefinitionReader);這個方法 AbstractXMLApplicationContext中有實現 我們看下:
跟蹤第五步;
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { //以Resource的方式獲得配置文件的資源位置
Resource[] configResources = getConfigResources();
if (configResources != null) { reader.loadBeanDefinitions(configResources); }
//以String的方式獲得配置文件的資源位置 String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
跟蹤第六步:這開始 就是BeanDefinitionReader模塊的實現了
這里的具體實現應該去 XmlBeanDefinitionReader中的loadBeadDefinitions,但是閱讀了源碼發現 這個方法是由XmlBeanDefinitionReader的基類 AbstractBeanDefinitionReader 來實現的我們看下:
@Override public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); //一個普通到不能再普通的for循環遍歷 int counter = 0; for (String location : locations) { //這是我們要繼繼續跟蹤的方法 counter += loadBeanDefinitions(location); } return counter; }
也就是我們的第七步:
loadBeanDefinitions(String )這個方法 還在AbstractBeanDefinitionReader的類中實現:
@Override public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); }
得 繼續往下看把 這沒啥好說的了
繼續跟蹤 上面代碼中的 loadBeanDefinitions(location, null);
第八步:
loadBeanDefinitions(String location, Set<Resource> actualResources);這個方法 還在AbstractBeanDefinitionReader類中:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { //這里取到的ResourceLoader 是DefaultResourceLoader ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } // 這里對Resource的路徑模式進行解析,比如我么設定的各種Ant格式的路徑定義,得到 //需要的Resource集合,這些Resource集合指定我們已經定義好的BeanDefinition信息,可以是多個文件 if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { //使用ResourceReader的 getResource(String)來獲取資源定位 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { actualResources.add(resource); } } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); } return loadCount; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { // 調用DefaultResourceLoader的getResource(String)方法來獲取資源定位 Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource); if (actualResources != null) {
//看這里哈 對於成功找到的Resource定位 都會添加到這個 傳入的 actualResources參數中 actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } }
繼續跟蹤第九步:DefaultResourceLoader 中的 getResource(String)實現
@Override public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); //重點來了 getResourceByPath是一個抽象方法 具體實現全部由具體子類實現 if (location.startsWith("/")) { return getResourceByPath(location); }// 這里處理帶有classPath標識的Resource else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // 這里處理帶有URL標識的Resource定位 URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { //如果既不是classPath 也不是URL標識的Resource定位(那其實就是自己實現的了).則把getResource的重任交給getResourceByPath來完成,這個方法是一個protected方法,默認的實現是得到一個ClassPathContextResource,這個方法常常會用子類來實現也就是
//我們舉例的FileSystemXMLApplicationContext return getResourceByPath(location); } } }
第十步:具體ApplicationContext實現中 且實現了DefaultResourceLoader的 容器 我們舉例子是FileSystemXMLApplicationContext
在回顧一下FileSystemXMLApplicationContext的繼承體系:
也就是說連AbstractApplicationContext 都是 DefaultResourceLoader的子類 那么 基本上所有ApplicationContext的具體實現 都繼承了DefaultResourceLoader
@Override protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
上面我們可以看到FileSystemXMLApplicationContext的getResourceBypath會返回一個FileSystemResource對象,通過這個對象Spring可以進行相關的I/O操作,完成BeanDefinition的定位,分析到這里已經一目了然了吧,它實現的就是對path的解析然后生成一個FileSystemResource對象返回,那么以上呢 就從IOC容器初始化開始 一直到 具體的資源定位的流程就分析完了
如果是其他的ApplicationContext,那么回對應生成其他種類的Resource,比如說ClassPathResource、ServletContextResource等,關於Spring中的Resource的種類,可以從Resource的繼承類中了解
一堆哈,這些接口對不同的Resource實現代表着不同的意義,是Resource的實現要考慮的,
通過對前面的實現原理的分析,我們以FileSystemXMLApplicationContext的實現原理為例子,了解了Resource定位問題的解決方案,即以FileSystemResource方式存在的Resource的定位實現,在BeanDefinition定位完成的基礎上,就可以通過返回的Resource對象進行BeanDefinition的載入了,在定位過程完成以后,為BeanDefinition的載入創造了I/O操作的條件,但是具體的數據還沒有開始讀取,這些數據的讀入將在下面的介紹BeanDefinition的載入和解析中完成,仍然以水桶為例子,這里就像用水桶去打水,先要找到水源,這里完成對Resource的定位,就類似於水源已經找到了,下面就是打水的過程了,類似於把找到的水裝到水桶里的過程,找水不簡單,但是與打水相比,我們發現打水更復雜一些。
明天繼續
BeanDefinition的載入 和 解析
在完成對代表BeanDefinition的Resource定位分析后,下面來了解一下整個BeanDefinition信息的載入過程,對IOC容器來說,這個載入過程,相當於把定義的BeanDefinition在IOC容器中轉化成一個Spring內部表示的數據結構的過程,IOC容器對Bean的管理和依賴注入功能的實現,是通過對其持有的BeanDefinition進行各種相關操作來完成的,這些BeanDefinition在IOC容器中通過一個HAHSMAP來保持和維護,當然這只是一種比較簡單的維護方法,如果需要提供IOC容器的性能 和 容量,完全可以自己做一些擴展,
下面就從DefaultListableBeanFactory的設計入手,來看看IOC容器是怎么樣完成BeanDefinition載入的,這個DefaultListableBeanFactory在前面已經碰到過多次,在開始分析之前,下回到IOC容器的初始化繞口,也就是看一下refresh方法,這個方法的最初是在FileSystemXmmlApplicationContext的構造函數中被調用的,它的調用標志着容器初始化過程的開始,這些初始化對象就是BeanDefinition數據,
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
對容器的啟動來說,refresh是一個很重要的方法,下面介紹一下它的實現,前面已經了解過這個這個方法 是在AbstractApplicationContext中實現的,他詳細的描述了整個ApplicationContext的初始化過程,比如BeanFactory的更新,MessageSource和PostProcessor的注冊,等等,這里看起來更像是對ApplicationContext的初始化的模板 或者 指定大綱,這個執行過程為Bean聲明周期管理提供了條件,熟悉IOC容器的賭注,從這一系列調用的名字就能大致了解應用上下文初始化的主要內容,
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { prepareRefresh(); //這里是在子類中啟動refreshBeanFactory的地方 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); prepareBeanFactory(beanFactory); try { // 設置BeanFactory的后置處理器 postProcessBeanFactory(beanFactory); //調用這些BeanFactory的后置處理器,這些處理器是在Bean定義中想容器注冊的 invokeBeanFactoryPostProcessors(beanFactory); // 注冊BeanFactory的后置處理器,在Bean創建過程中調用 registerBeanPostProcessors(beanFactory); //初始化上下文中的所有消息源 initMessageSource(); //初始化上下文的事件機制 initApplicationEventMulticaster(); // 初始化其他特殊的Bean onRefresh(); // 檢查是監聽器實現的Bean 把他們注冊到容器中 registerListeners(); // 實例化所有的(no-lazy-init)單例Bean finishBeanFactoryInitialization(beanFactory); //發布容器事件 結束refresh過程 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } //銷毀之前創建的單例bean destroyBeans(); //重置‘active’標志 cancelRefresh(ex); throw ex; } } }
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
進入到AbstractRefreshableApplicationContext中的refreshBeanFactory方法中,,在這個方法中,在創建IOC容器之前,如果已經有容器存在,那么需要把已有的容器銷毀和關閉的,保證在refresh方法以后使用的都是新建立起來的IOC容器,這么看,這個refresh非常像重啟容器,就像重啟計算機那樣,在建立好當前的IOC容器之后,開始了對容器的初始化過程,比如BeanDefinition的載入、解析。
protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); //載入和解析BeanDefinition的入口 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
這里調用的loadBaenDefinitions 是一個抽象方法,我們看看前面提到的loadBeanDefinition在AbstractRefreshableApplicationContext的子類AbstractXMLApplicationContext中的實現,在這個loadBeanDefinitions中,初始化了一個XmlBeanDefinitionReader ,然后把這個讀取器在IOC容器中設置還(過程和編程式使用XMLBeanFactory是類似),最后是啟動讀取器來完成BeanDefinition在IOC容器中的載入。
@Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 創建XmlBeanDefinitionReader,並通過回調設置到BeanFactory中,這里使用的IOC容器 也是DefaultListableBeanFactory XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); //這里設置剛初始化的XmlBeanDefinitionReader,為其配置ResourceLoader,因為凡是以AbstractApplicationContext為基類的容器 都默認繼承了DefaultResourceLoader,所以這里傳一個this beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, //啟動BeanDefinition的載入 initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }
接着就是loadBeanDefinitions調用的地方,首先得到BeanDefinition信息的Resource定位,然后直接調用XmlBeanDefinitionReader來讀取,具體的讀取過程是委托給BeanDefinitionReader來完成的,因為這里的BeanDefinition信息是通過XML文件來定義的,所以使用XmlBeanDefinitionReader來載入BeanDefinition到容器中。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { //以Resource的形式獲取配置文件的資源定位 Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } //以String的形式獲取配置文件的資源定位 String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
看上面的方法 其實就是獲取到准確定位之后 使用XmlBeanDefinitionReader的loadBeanDefinitions方法 來載入BeanDefinition信息了,這和之前的編程式使用XMLBeanFactory就一樣了。
小結: 通過以上實現原理的分析,我們可以看到,在初始化FileSystemXMLApplicationContext的過程中 調用 refresh來啟動整個BeanDefinition的載入過程的,這個初始化是通過定義一個XmlBeanDefinitionReader來作為一個BeanDefinitionReader來作為讀取器,同時我們也知道了實際上使用的容器 也是DefaultListableBeanFactory,具體的Resource的載入在XmlBeanDefinitionReader讀取BeanDefinition時 實現,因為Spring可以對應不同形式的BeanDefinition定義,由於這里使用的是Xml方式的定義,所以需要使用XmlBeanDefinitionReader,如果使用了其他種類形式的BeanDefinition定義,就需要使用其他對應的BeanDefinitionReader來完成數據的載入工作,在XmlBeanDefinitionReader 的實現中可以看到,是在reader.loadBeanDefinitions中開始記性BeanDefinition的載入的,而這時XmlBeanDefinitionReader的父親AbstractBeanDefinitionReader已經為了BeanDefinition的載入做好了准備,
也就是 reader.loadBeanDefinitions(Resource[] re);reader.loadBeanDefinitions(String[] s);的具體實現 是在所有BeanDefinitionReader的基類中完成的AbstractBeanDefinitionRedaer。
@Override public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); int counter = 0; for (String location : locations) { counter += loadBeanDefinitions(location); } return counter; } @Override public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; for (Resource resource : resources) { counter += loadBeanDefinitions(resource); } return counter; }
上面這類方法 的作用其實就是做一個遍歷 將獲得的所有資源定位全都加載一遍,具體的調用時loadBeanDefinitions(String s)或者 loadBeanDefinitions(Resource r),這個方法 是一個抽象方法,這就是要交給其不同的子類去實現了,
那么因為我們使用的XmlBeanDefinitionReader 所以這個方法 我們去XmlBeanDefinitionReader去找下,在讀取器中,需要得到代表XML文件的Resource對象,因為這個Resource對象封裝了對XML文件的I/O操作,隨意讀取器可以在打開I/O留后得到Xml的文件對象,
有了這個文件之后,就可以照Spring的Bean定義規則來對這個xml的文檔樹進行解析了,這個解析是交給BeanDefinitionParserDelegate來完成的,看起來實現脈絡很清楚,具體看代碼把:
@Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { //根據指定資源對象來加載BeanDefinition的入口 return loadBeanDefinitions(new EncodedResource(resource)); }
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try {//這里得到定義BeanDefinition的Xml文件 的輸入流 InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); }//這里是具體讀取Xml文件的方法 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
下面就是看下 具體讀取Xml文件的 方法,也是從指定xml文件中實際載入BeanDefinition的地方。當然了 這肯定是在XmlBeanDefinitionReader中的方法了,這屬於具體BeanDefinitionReader自己要實現的功能;
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //這里取得的是XML文件的Document對象,具體的解析過程是由DocumentLoader完成的, //這里使用的DocumentLoader是DefaultDocumentLoader,在定義documentLoader對象時候創建的 Document doc = doLoadDocument(inputSource, resource); //這里啟動的是對BeanDefinition解析的詳細過程,這個解析會用到Spring的Bean配置規則,是我門下面詳細講解的內容,
return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
我們這次主要關心的是Spring的BeanDefinition是怎么樣按照Spring的Bean語義要求進行解析 並轉化為容器內部數據結構的,這個過程是在registerBeanDefinitions(doc, resource)中完成的,具體的過程是BeanDefinitionDocumentReader來完成的,這個registerBeanDefinitions還對載入的Bean數量進行了統計,這個方法也是在 XmlBeanDefinitionReader 中自己實現的,
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //這里得到的BeanDefinitionDocumentReader對象來對XML的BeanDefinition信息進行解析 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); //具體的解析過程在這個方法中實現 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
小結:BeanDefinition的載入分成兩部分,首先通過調用XML的解析器(XmlBeanDefinitionReader)得到document對象,但這些document對象並沒有 按照Spring的Bean規則去進行解析,在完成通用XML解析之后才是按照Spring得 Bean規則進行解析的地方,這個按照Spring的Bean規則進行解析的過程是在documentReade中實現的,這里使用的documentReader是默認設置好的DefaultBeanDefinitionDocumentReader 創建的過程也是在XmlBeanDefinitionReader 中完成的,根據指定的默認private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class; class對象生成一個DefaultBeanDefinitionDocumentReader對象,具體的解析過程在DefaultBeanDefinitionDocumentReader來實現,我們繼續跟蹤:
*/ public interface BeanDefinitionDocumentReader { /** * Set the Environment to use when reading bean definitions. * <p>Used for evaluating profile information to determine whether a * {@code <beans/>} document/element should be included or ignored. * @deprecated in favor of {@link XmlReaderContext#getEnvironment()} */ @Deprecated void setEnvironment(Environment environment); /** * Read bean definitions from the given DOM document and * register them with the registry in the given reader context. * @param doc the DOM document * @param readerContext the current context of the reader * (includes the target registry and the resource being parsed) * @throws BeanDefinitionStoreException in case of parsing errors */ void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) throws BeanDefinitionStoreException; }
BeanDefinitionDocumentReader中有兩個抽象方法 registerBeanDefinitions 這個方法交給其子類去實現,所以 是DefaultBeanDefinitionDocumentReader中自己實現的,
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
這里只是將 XML中的元素取了出來 具體的活還是 doRegisterBeanDefinitions(root);來實現的 do開頭的方法才是真正干活的方法,
protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { return; } } } //真正的解析BeanDefinition之前做的一些事情的接口觸發 preProcessXml(root); parseBeanDefinitions(root, this.delegate);
// 解析BeanDefinition之后可以做的一些事情的觸發 postProcessXml(root); this.delegate = parent; }
而真正去解析Document文檔樹的是 BeanDefinitionParserDelegate完成的,這個解析過程是與Spring對BeanDefinition的配置規則緊密相關的,
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
遍歷