- Spring技術內幕:深入解析Spring架構與設計原理(第2版)
- 計文柯
- 6366字
- 2018-12-31 19:55:35
2.2 IoC容器系列的設計與實現:BeanFactory和ApplicationContext
在Spring IoC容器的設計中,我們可以看到兩個主要的容器系列,一個是實現BeanFactory接口的簡單容器系列,這系列容器只實現了容器的最基本功能;另一個是ApplicationContext應用上下文,它作為容器的高級形態而存在。應用上下文在簡單容器的基礎上,增加了許多面向框架的特性,同時對應用環境作了許多適配。有了這兩種基本的容器系列,基本上可以滿足用戶對IoC容器使用的大部分需求了。下面,我們就對Spring IoC容器中這兩種容器系列的設計與實現進行一個簡要的分析。
2.2.1 Spring的IoC容器系列
IoC容器為開發者管理對象之間的依賴關系提供了很多便利和基礎服務。有許多IoC容器供開發者選擇,SpringFramework的IoC核心就是其中一個,它是開源的。那具體什么是IoC容器呢?它在Spring框架中到底長什么樣?其實對IoC容器的使用者來說,我們經常接觸到的BeanFactory和ApplicationContext都可以看成是容器的具體表現形式。如果深入到Spring的實現中去看,我們通常所說的IoC容器,實際上代表著一系列功能各異的容器產品,只是容器的功能有大有小,有各自的特點。我們以水桶為例,在商店中出售的水桶有大有小,制作材料也各不相同,有金屬的、塑料的等,總之是各式各樣的,但只要能裝水,具備水桶的基本特性,那就可以作為水桶來出售,來讓用戶使用。這在Spring中也是一樣,Spring有各式各樣的IoC容器的實現供用戶選擇和使用。使用什么樣的容器完全取決于用戶的需要,但在使用之前如果能夠了解容器的基本情況,那對容器的使用是非常有幫助的,就像我們在購買商品前對商品進行考察和挑選那樣。從代碼的角度入手,我可以看到關于這一系列容器的設計情況。
圖2-1展示了這個容器系列的概況。

圖2-1 Spring的IoC容器系列概況
就像商品需要有產品規格說明一樣,同樣,作為IoC容器,也需要為它的具體實現指定基本的功能規范,這個功能規范的設計表現為接口類BeanFactory,它體現了Spring為提供給用戶使用的IoC容器所設定的最基本的功能規范。還是以前面的百貨商店出售水桶為例,如果把IoC容器看成一個水桶,那么這個BeanFactory就定義了可以作為水桶的基本功能,比如至少能裝水,有個提手等。除了滿足基本的功能,為了不同場合的需要,水桶的生產廠家還在這個基礎上為用戶設計了其他各式各樣的水桶產品,以滿足不同的用戶需求。這些水桶會提供更豐富的功能,有簡約型的,有豪華型的,等等。但是,不管是什么水桶,它都需要有一項最基本的功能:能夠裝水。那對Spring的具體IoC容器實現來說,它需要滿足的基本特性是什么呢?它需要滿足BeanFactory這個基本的接口定義,所以在圖2-1中可以看到這個BeanFactory接口在繼承體系中的地位,它是作為一個最基本的接口類出現在Spring的IoC容器體系中的。
在這些Spring提供的基本IoC容器的接口定義和實現的基礎上,Spring通過定義BeanDefinition來管理基于Spring的應用中的各種對象以及它們之間的相互依賴關系。BeanDefinition抽象了我們對Bean的定義,是讓容器起作用的主要數據類型。我們都知道,在計算機世界里,所有的功能都是建立在通過數據對現實進行抽象的基礎上的。IoC容器是用來管理對象依賴關系的,對IoC容器來說,BeanDefinition就是對依賴反轉模式中管理的對象依賴關系的數據抽象,也是容器實現依賴反轉功能的核心數據結構,依賴反轉功能都是圍繞對這個BeanDefinition的處理來完成的。這些BeanDefinition就像是容器里裝的水,有了這些基本數據,容器才能夠發揮作用。在下面的分析中,BeanDefinition的出現次數會很多。
同時,在使用IoC容器時,了解BeanFactory和ApplicationContext之間的區別對我們理解和使用IoC容器也是比較重要的。弄清楚這兩種重要容器之間的區別和聯系,意味著我們具備了辨別容器系列中不同容器產品的能力。還有一個好處就是,如果需要定制特定功能的容器實現,也能比較方便地在容器系列中找到一款恰當的產品作為參考,不需要重新設計。
2.2.2 Spring IoC容器的設計
在前面的小節中,我們了解了IoC容器系列的概況。在Spring中,這個IoC容器是怎樣設計的呢?我們可以看一下如圖2-2所示的IoC容器的接口設計圖,這張圖描述了IoC容器中的主要接口設計。

圖2-2 IoC容器的接口設計圖
下面對接口關系做一些簡要的分析,可以依據以下內容來理解這張接口設計圖。
? 從接口BeanFactory到HierarchicalBeanFactory,再到ConfigurableBeanFactory,是一條主要的BeanFactory設計路徑。在這條接口設計路徑中,BeanFactory接口定義了基本的IoC容器的規范。在這個接口定義中,包括了getBean()這樣的IoC容器的基本方法(通過這個方法可以從容器中取得Bean)。而HierarchicalBeanFactory接口在繼承了BeanFactory的基本接口之后,增加了getParentBeanFactory()的接口功能,使BeanFactory具備了雙親IoC容器的管理功能。在接下來的ConfigurableBeanFactory接口中,主要定義了一些對BeanFactory的配置功能,比如通過setParentBeanFactory()設置雙親IoC容器,通過addBeanPostProcessor()配置Bean后置處理器,等等。通過這些接口設計的疊加,定義了BeanFactory就是簡單IoC容器的基本功能。關于BeanFactory簡單IoC容器的設計,我們會在后面的內容中詳細介紹。
? 第二條接口設計主線是,以ApplicationContext應用上下文接口為核心的接口設計,這里涉及的主要接口設計有,從BeanFactory到ListableBeanFactory,再到ApplicationContext,再到我們常用的WebApplicationContext或者ConfigurableApplicationContext接口。我們常用的應用上下文基本上都是ConfigurableApplicationContext或者WebApplicationContext的實現。在這個接口體系中,ListableBeanFactory和HierarchicalBeanFactory兩個接口,連接BeanFactory接口定義和ApplicationConext應用上下文的接口定義。在ListableBeanFactory接口中,細化了許多BeanFactory的接口功能,比如定義了getBeanDefinitionNames()接口方法;對于HierarchicalBeanFactory接口,我們在前文中已經提到過;對于ApplicationContext接口,它通過繼承MessageSource、ResourceLoader、ApplicationEventPublisher接口,在BeanFactory簡單IoC容器的基礎上添加了許多對高級容器的特性的支持。
? 這里涉及的是主要接口關系,而具體的IoC容器都是在這個接口體系下實現的,比如DefaultListableBeanFactory,這個基本IoC容器的實現就是實現了Configurable-BeanFactory,從而成為一個簡單IoC容器的實現。像其他IoC容器,比如XmlBeanFactory,都是在DefaultListableBeanFactory的基礎上做擴展,同樣地,ApplicationContext的實現也是如此。
? 這個接口系統是以BeanFactory和ApplicationContext為核心的。而BeanFactory又是IoC容器的最基本接口,在ApplicationContext的設計中,一方面,可以看到它繼承了BeanFactory接口體系中的ListableBeanFactory、AutowireCapableBeanFactory、HierarchicalBeanFactory等BeanFactory的接口,具備了BeanFactory IoC容器的基本功能;另一方面,通過繼承MessageSource、ResourceLoadr、ApplicationEventPublisher這些接口,BeanFactory為ApplicationContext賦予了更高級的IoC容器特性。對于ApplicationContext而言,為了在Web環境中使用它,還設計了WebApplicationContext接口,而這個接口通過繼承ThemeSource接口來擴充功能。
1. BeanFactory的應用場景
BeanFactory提供的是最基本的IoC容器的功能,關于這些功能定義,我們可以在接口BeanFactory中看到。
BeanFactory接口定義了IoC容器最基本的形式,并且提供了IoC容器所應該遵守的最基本的服務契約,同時,這也是我們使用IoC容器所應遵守的最底層和最基本的編程規范,這些接口定義勾畫出了IoC的基本輪廓。很顯然,在Spring的代碼實現中,BeanFactory只是一個接口類,并沒有給出容器的具體實現,而我們在圖2-1中看到的各種具體類,比如DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等都可以看成是容器附加了某種功能的具體實現,也就是容器體系中的具體容器產品。下面我們來看看BeanFactory是怎樣定義IoC容器的基本接口的。
用戶使用容器時,可以使用轉義符“&”來得到FactoryBean本身,用來區分通過容器來獲取FactoryBean產生的對象和獲取FactoryBean本身。舉例來說,如果myJndiObject是一個FactoryBean,那么使用&myJndiObject得到的是FactoryBean,而不是myJndiObject這個FactoryBean產生出來的對象。關于具體的FactoryBean的設計和實現模式,我們會在后面的章節中介紹。
注意 理解上面這段話需要很好地區分FactoryBean和BeanFactory這兩個在Spring中使用頻率很高的類,它們在拼寫上非常相似。一個是Factory,也就是IoC容器或對象工廠;一個是Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IoC容器)來進行管理的。但對FactoryBean而言,這個Bean不是簡單的Bean,而是一個能產生或者修飾對象生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式類似。
BeanFactory接口設計了getBean方法,這個方法是使用IoC容器API的主要方法,通過這個方法,可以取得IoC容器中管理的Bean,Bean的取得是通過指定名字來索引的。如果需要在獲取Bean時對Bean的類型進行檢查,BeanFactory接口定義了帶有參數的getBean方法,這個方法的使用與不帶參數的getBean方法類似,不同的是增加了對Bean檢索的類型的要求。
用戶可以通過BeanFactory接口方法中的getBean來使用Bean名字,從而在獲取Bean時,如果需要獲取的Bean是prototype類型的,用戶還可以為這個prototype類型的Bean生成指定構造函數的對應參數。這使得在一定程度上可以控制生成prototype類型的Bean。有了BeanFactory的定義,用戶可以執行以下操作:
? 通過接口方法containsBean讓用戶能夠判斷容器是否含有指定名字的Bean。
? 通過接口方法isSingleton來查詢指定名字的Bean是否是Singleton類型的Bean。對于Singleton屬性,用戶可以在BeanDefinition中指定。
? 通過接口方法isPrototype來查詢指定名字的Bean是否是prototype類型的。與Singleton屬性一樣,這個屬性也可以由用戶在BeanDefinition中指定。
? 通過接口方法isTypeMatch來查詢指定了名字的Bean的Class類型是否是特定的Class類型。這個Class類型可以由用戶來指定。
? 通過接口方法getType來查詢指定名字的Bean的Class類型。
? 通過接口方法getAliases來查詢指定了名字的Bean的所有別名,這些別名都是用戶在BeanDefinition中定義的。
這些定義的接口方法勾畫出了IoC容器的基本特性。因為這個BeanFactory接口定義了IoC容器,所以下面給出它定義的全部內容供大家參考,如代碼清單2-1所示。
代碼清單2-1 BeanFactory接口
public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&"; Object getBean(String name) throws BeansException; <T> T getBean(String name, Class<T> requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException; boolean containsBean(String name); boolean isSingleton(String name) throws NoSuchBeanDefinitionException; boolean isPrototype(String name) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException; Class getType(String name) throws NoSuchBeanDefinitionException; String[] getAliases(String name); }
可以看到,這里定義的只是一系列的接口方法,通過這一系列的BeanFactory接口,可以使用不同的Bean的檢索方法,很方便地從IoC容器中得到需要的Bean,從而忽略具體的IoC容器的實現,從這個角度上看,這些檢索方法代表的是最為基本的容器入口。
2. BeanFactory容器的設計原理
BeanFactory接口提供了使用IoC容器的規范。在這個基礎上,Spring還提供了符合這個IoC容器接口的一系列容器的實現供開發人員使用。我們以XmlBeanFactory的實現為例來說明簡單IoC容器的設計原理。如圖2-3所示為XmlBeanFactory設計的類繼承關系。

圖2-3 XmlBeanFactory設計的類繼承關系
可以看到,作為一個簡單IoC容器系列最底層實現的XmlBeanFactory,與我們在Spring應用中用到的那些上下文相比,有一個非常明顯的特點:它只提供最基本的IoC容器的功能。理解這一點有助于我們理解ApplicationContext與基本的BeanFactory之間的區別和聯系。我們可以認為直接的BeanFactory實現是IoC容器的基本形式,而各種ApplicationContext的實現是IoC容器的高級表現形式。關于ApplicationContext的分析,以及它與BeanFactory相比的增強特性都會在下面進行詳細的分析。
讓我們好好地看一下圖2-3中的繼承關系,從中可以清楚地看到類之間的聯系,它們都是IoC容器系列的組成部分。在設計這個容器系列時,我們可以從繼承體系的發展上看到IoC容器各項功能的實現過程。如果要擴展自己的容器產品,建議讀者最好在繼承體系中檢查一下,看看Spring是不是已經提供了現成的或相近的容器實現供我們參考。下面就從我們比較熟悉的XmlBeanFactory的實現入手進行分析,來看看一個基本的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文件形式存在的,那么可以使用像“ClassPath-Resource res = new ClassPathResource("beans.xml");”這樣具體的ClassPathResource來構造需要的Resource,然后將Resource作為構造參數傳遞給XmlBeanFactory構造函數。這樣,IoC容器就可以方便地定位到需要的BeanDefinition信息來對Bean完成容器的初始化和依賴注入過程。
XmlBeanFactory的功能是建立在DefaultListableBeanFactory這個基本容器的基礎上的,并在這個基本容器的基礎上實現了其他諸如XML讀取的附加功能。對于這些功能的實現原理,看一看XmlBeanFactory的代碼實現就能很容易地理解。如代碼清單2-2所示,在XmlBeanFactory構造方法中需要得到Resource對象。對XmlBeanDefinitionReader對象的初始化,以及使用這個對象來完成對loadBeanDefinitions的調用,就是這個調用啟動從Resource中載入BeanDefinitions的過程,LoadBeanDefinitions同時也是IoC容器初始化的重要組成部分。
代碼清單2-2 XmlBeanFactory的實現
public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } }
我們看到XmlBeanFactory使用了DefaultListableBeanFactory作為基類,DefaultListable-BeanFactory是很重要的一個IoC實現,在其他IoC容器中,比如ApplicationContext,其實現的基本原理和XmlBeanFactory一樣,也是通過持有或者擴展DefaultListableBeanFactory來獲得基本的IoC容器的功能的。
參考XmlBeanFactory的實現,我們以編程的方式使用DefaultListableBeanFactory。從中我們可以看到IoC容器使用的一些基本過程。盡管我們在應用中使用IoC容器時很少會使用這樣原始的方式,但是了解一下這個基本過程,對我們了解IoC容器的工作原理是非常有幫助的。因為這個編程式使用容器的過程,很清楚揭示了在IoC容器實現中的那些關鍵的類(比如Resource、DefaultListableBeanFactory和BeanDefinitionReader)之間的相互關系,例如它們是如何把IoC容器的功能解耦的,又是如何結合在一起為IoC容器服務的,等等。在代碼清單2-3中可以看到編程式使用IoC容器的過程。
代碼清單2-3 編程式使用IoC容器
ClassPathResource res = new ClassPathResource("beans.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(res);
這樣,我們就可以通過factory對象來使用DefaultListableBeanFactory這個IoC容器了。在使用IoC容器時,需要如下幾個步驟:
1)創建IoC配置文件的抽象資源,這個抽象資源包含了BeanDefinition的定義信息。
2)創建一個BeanFactory,這里使用DefaultListableBeanFactory。
3)創建一個載入BeanDefinition的讀取器,這里使用XmlBeanDefinitionReader來載入XML文件形式的BeanDefinition,通過一個回調配置給BeanFactory。
4)從定義好的資源位置讀入配置信息,具體的解析過程由XmlBeanDefinitionReader來完成。完成整個載入和注冊Bean定義之后,需要的IoC容器就建立起來了。這個時候就可以直接使用IoC容器了。
3. ApplicationContext的應用場景
上一節中我們了解了IoC容器建立的基本步驟。理解這些步驟之后,可以很方便地通過編程的方式來手工控制這些配置和容器的建立過程了。但是,在Spring中,系統已經為用戶提供了許多已經定義好的容器實現,而不需要開發人員事必躬親。相比那些簡單拓展BeanFactory的基本IoC容器,開發人員常用的ApplicationContext除了能夠提供前面介紹的容器的基本功能外,還為用戶提供了以下的附加服務,可以讓客戶更方便地使用。所以說,ApplicationContext是一個高級形態意義的IoC容器,如圖2-4所示,可以看到ApplicationContext在BeanFactory的基礎上添加的附加功能,這些功能為ApplicationContext提供了以下BeanFactory不具備的新特性。

圖2-4 ApplicationContext的接口關系
? 支持不同的信息源。我們看到ApplicationContext擴展了MessageSource接口,這些信息源的擴展功能可以支持國際化的實現,為開發多語言版本的應用提供服務。
? 訪問資源。這一特性體現在對ResourceLoader和Resource的支持上,這樣我們可以從不同地方得到Bean定義資源。這種抽象使用戶程序可以靈活地定義Bean定義信息,尤其是從不同的I/O途徑得到Bean定義信息。這在接口關系上看不出來,不過一般來說,具體ApplicationContext都是繼承了DefaultResourceLoader的子類。因為DefaultResourceLoader是AbstractApplicationContext的基類,關于Resource在IoC容器中的使用,后面會有詳細的講解。
? 支持應用事件。繼承了接口ApplicationEventPublisher,從而在上下文中引入了事件機制。這些事件和Bean的生命周期的結合為Bean的管理提供了便利。
? 在ApplicationContext中提供的附加服務。這些服務使得基本IoC容器的功能更豐富。因為具備了這些豐富的附加功能,使得ApplicationContext與簡單的BeanFactory相比,對它的使用是一種面向框架的使用風格,所以一般建議在開發應用時使用ApplicationContext作為IoC容器的基本形式。
4. ApplicationContext容器的設計原理
在ApplicationContext容器中,我們以常用的FileSystemXmlApplicationContext的實現為例來說明ApplicationContext容器的設計原理。
在FileSystemXmlApplicationContext的設計中,我們看到ApplicationContext應用上下文的主要功能已經在FileSystemXmlApplicationContext的基類AbstractXmlApplicationContext中實現了,在FileSystemXmlApplicationContext中,作為一個具體的應用上下文,只需要實現和它自身設計相關的兩個功能。
一個功能是,如果應用直接使用FileSystemXmlApplicationContext,對于實例化這個應用上下文的支持,同時啟動IoC容器的refresh()過程。這在FileSystemApplicationContext的代碼實現中可以看到,代碼如下:
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
這個refresh()過程會牽涉IoC容器啟動的一系列復雜操作,同時,對于不同的容器實現,這些操作都是類似的,因此在基類中將它們封裝好。所以,我們在FileSystemXml的設計中看到的只是一個簡單的調用。關于這個refresh()在IoC容器啟動時的具體實現,是后面要分析的主要內容,這里就不詳細展開了。
另一個功能是與FileSystemXmlApplicationContext設計具體相關的功能,這部分與怎樣從文件系統中加載XML的Bean定義資源有關。
通過這個過程,可以為在文件系統中讀取以XML形式存在的BeanDefinition做準備,因為不同的應用上下文實現對應著不同的讀取BeanDefinition的方式,在FileSystemXml-ApplicationContext中的實現代碼如下:
protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
可以看到,調用這個方法,可以得到FileSystemResource的資源定位。
- Vue 3移動Web開發與性能調優實戰
- DB2 V9權威指南
- Qt 5 and OpenCV 4 Computer Vision Projects
- CockroachDB權威指南
- LabVIEW Graphical Programming Cookbook
- Learning AWS Lumberyard Game Development
- Vue.js 3.0源碼解析(微課視頻版)
- Python Geospatial Development(Second Edition)
- 教孩子學編程:C++入門圖解
- 概率成形編碼調制技術理論及應用
- Learning Laravel's Eloquent
- Visual Basic程序設計實驗指導(第二版)
- Python機器學習算法與應用
- Web前端開發最佳實踐
- 算法超簡單:趣味游戲帶你輕松入門與實踐