- Spring技術內幕:深入解析Spring架構與設計
- 計文柯
- 59字
- 2018-12-31 15:02:43
第一部分 Spring核心實現篇
本篇將對Spring的核心IoC容器和AOP的實現原理進行闡述。IoC容器和AOP是Spring的核心,是Spring系統中其他組件模塊和應用開發的基礎。從兩個核心模塊的設計和實現上可以了解到Spring倡導的對企業應用開發所應秉持的思路,比如使用POJO開發企業應用、提供一致的編程模型、強調對接口編程等。對于這些Spring背后的開發思想和設計理念,大家都不會陌生,在Rod Johnson的經典著作里都有全面和深刻的講解。作為參考,我們可以看到Spring官方網站對Spring項目的描述。如下圖所示,Spring的目標和愿景寫得很清楚。

首先,Spring的目標在于讓Java EE的開發變得更容易,這也就意味著Spring框架的使用也應該是容易的。對于開發人員而言,易用性是第一位的。為什么要讓Java EE開發變得更容易,難道以前的Java EE開發很艱難?Spring究竟是如何讓Java EE的開發變得更容易的呢?了解Java EE開發歷史的讀者都知道,正如Rod Johnson在他的著作Expert One-on-One Java EE Design and Development中提到的那樣,EJB模型為Java EE開發引入了過度的復雜性,這個開發模型對Java EE的開發并不友好。有沒有更好的開發模型呢?有,就是POJO!它讓Java洗凈鉛華,恢復其自然的風采。使用POJO不僅能開發復雜的Java企業應用,而且還可以讓Java EE開發在開發成本、開發周期、可維護性和性能上獲得更大優勢。對一般的企業應用需求而言,重要的是如何方便地使用應用需要的服務,而不是各種各樣的開發模型和模式。雖然這些模式為我們描繪了設計高可靠性分布式應用的美妙場景,但這些場景是不是大多數企業應用開發者所要面對的呢?
世上都說Java好,唯有Spring忘不了。喜歡Java,是因為它簡潔,不但包含了面向對象的語言特性,同時還可以跨平臺,可謂是簡潔而又強大。但是,進入到企業應用后,作為門外漢的自己一看到復雜的EJB模型就心生畏懼。這時候,我接觸到了Spring,她給人的第一印象就是簡潔卻又具有豐富的內涵,就像第一次遇到Java一樣,被她的這種特質深深地吸引了。她降低了企業應用開發的門檻,還原了POJO的本色,讓我們直接依賴于Java語言,直接依賴于面向對象編程,使用無所不在的單元測試來保證代碼質量,這樣我們就有信心能夠開發出高質量的企業應用。
也就是說,我們如何才能讓開發既變得容易,又能享受到Java EE中提供的各種服務呢?Spring的目標就是通過自己的努力,讓用戶體會到這種簡單之中的強大。同時,作為應用框架,Spring不想把自己作為另外一種復雜開發模型的替代,也就是說不是用另一種復雜性去替代現有的復雜性,那是換湯不換藥,并不能解決問題。這就意味著需要有新的突破。要解決這個問題,需要降低應用的負載和框架的侵入性,Spring是怎樣做到這一點的呢?
Spring為我們提供的解決方案就是IoC容器和AOP支持。作為依賴反轉模式的具體實現,IoC容器很好地降低了框架的侵入性,同時也可以認為依賴反轉模式是Spring體現出來的核心模式。這些核心模式是軟件架構設計中非常重要的因素,比如說,我們常常看到的MVC模式就是這樣的核心模式。不要小看這些體系結構模式的作用和影響,它們就是框架背后所謂的“道”。有了IoC容器和AOP的支持,用戶的開發方式發生了很大的變化,具體說來,就是可以使用POJO來完成開發,對用戶來說是簡化了,但由于有平臺的支持,依然能夠實現復雜的企業應用開發。對于依賴反轉,在Spring中,Java EE的服務都被抽象到IoC容器和AOP中并進行了有效地封裝,而且因為依賴注入的特性,這些復雜的依賴關系的管理被反轉了,它們的管理交給了容器。
Spring中各個模塊的依賴關系可以用簡單的IoC配置文件進行描述,信息集中并且明了。在使用其他組件服務時,只需要在配置文件中配置這些服務與應用組件的依賴關系。對應用開發而言,只需要了解服務的接口和依賴關系的配置。這樣一來又很好地體現了Spring的第二個信條:讓應用開發對接口編程,而不是對類編程。這樣POJO使用Java EE服務時,可以將對這些服務實現的依賴降到最低,盡可能地降低框架的侵入性。
在處理與現有優秀解決方案的關系時,根據Spring的既定策略,它不會與這些第三方的解決方案發生競爭,而是致力于為應用提供使用優秀方案的集成平臺。真正地把Spring定位在應用平臺的地位,使得自己成為一個兼容并包的開放體系的同時,最大程度地降低開發者對Spring API的依賴,這是怎樣實現的呢?答案還是IoC容器和AOP技術,也就是說,Spring API在開發過程中并不是必須使用的。
第2章 SpringFramework的核心:IoC容器的實現
朝辭白帝彩云間,千里江陵一日還。
兩岸猿聲啼不住,輕舟已過萬重山。
—【唐】李白《早發白帝城》
2.1 Spring IoC容器概述
2.1.1 IoC容器和依賴反轉模式
子曰:溫故而知新。在這里,我們先簡要地回顧一下有關依賴反轉的相關概念。我們選取維基百科中關于依賴反轉的敘述,把這些文字作為我們理解依賴反轉概念的參考。這里不會對這些原理進行學理上的考究,只是希望提供一些有用的信息,以便給讀者一些啟示。這個模式非常重要,它是IoC容器得到廣泛應用的基礎。
維基百科對“依賴反轉”相關概念的敘述
早在2004年,Martin Fowler就提出了“哪些方面的控制被反轉了?”這個問題。他得出的結論是:依賴對象的獲得被反轉了。基于這個結論,他為控制反轉創造了一個更好的名字:依賴注入。許多非凡的應用(比HelloWorld.java更加優美、更加復雜)都是由兩個或多個類通過彼此的合作來實現業務邏輯,這使得每個對象都需要與其合作的對象(也就是它所依賴的對象)的引用。如果這個獲取過程要靠自身實現,那么如你所見,這將導致代碼高度耦合并且難以測試。
以上的這段話概括了依賴反轉的要義,如果合作對象的引用或依賴關系的管理要由具體對象來完成,會導致代碼的高度耦合和可測試性降低,這對復雜的面向對象系統的設計是非常不利的。在面向對象系統中,對象封裝了數據和對數據的處理,對象的依賴關系常常體現在對數據和方法的依賴上。這些依賴關系可以通過把對象的依賴注入交給框架或IoC容器來完成,這種從具體對象手中交出控制的做法是非常有價值的,它可以在解耦代碼的同時提高代碼的可測試性。極限編程中對單元測試和重構等實踐的強調體現了軟件開發過程中對質量的承諾,這是軟件項目成功的一個重要因素。
依賴控制反轉的實現方式有很多種。在Spring中,IoC容器是實現這個模式的載體,它可以在對象生成或初始化時直接將數據注入到對象中,也可以通過將對象引用注入到對象數據域中的方式來注入對方法調用的依賴。這種依賴注入是可以遞歸的,對象被逐層注入。就此而言,這種方案有一種完整而簡潔的美感,它把對象的依賴關系有序地建立起來,簡化了對象依賴關系的管理,在很大程度上簡化了面向對象系統的復雜性。
關于如何反轉對依賴的控制,把控制權從具體業務對象手中轉交到平臺或者框架中,是解決面向對象系統設計復雜性和提高面向對象系統可測試性的一個有效的解決方案。它促進了IoC設計模式的發展,是IoC容器要解決的核心問題。同時,也是產品化的IoC容器出現的推動力。
注意 IoC亦稱為“依賴倒置原理”(Dependency Inversion Principle),幾乎所有框架都使用了倒置注入(Martin Fowler)技巧,是IoC原理的一項應用。SmallTalk、C++、Java或.NET等面向對象語言的程序員已使用了這些原理。控制反轉是Spring框架的核心。
IoC原理的應用在不同的語言中有許多實現,比如SmallTalk、C++、Java等。在同一語言的實現中也會有多個具體的產品,Spring是Java語言實現中最著名的一個。同時,IoC也是Spring框架要解決的核心問題。
注意 應用控制反轉后,當對象被創建時,由一個調控系統內的所有對象的外界實體將其所依賴的對象的引用傳遞給它。也就是說,依賴被注入到對象中。所以,控制反轉是關于一個對象如何獲取它所依賴的對象的引用的,在這里,反轉指的是責任的反轉。
我們可以認為上面提到的調控系統是應用平臺,或者更具體地說是IoC容器。通過使用IoC容器,對象依賴關系的管理被反轉了,轉到IoC容器中來了,對象之間的相互依賴關系由IoC容器進行管理,并由容器完成對象的注入。這樣就在很大程度上簡化了應用的開發,把應用從復雜的對象依賴關系管理中解放出來。簡單地說,因為很多對象的依賴關系的建立和維護并不需要和系統運行狀態有很強的關聯性,所以可以把我們在面向對象編程中常常需要執行的諸如新建對象、給對象引用賦值等操作交由容器統一完成。這樣一來,這些散落在不同代碼中的功能相同的部分就集中成為容器的一部分,也就是成為面向對象系統的基礎設施的一部分。
如果對面向對象系統中的對象進行簡單地分類,會發現除了一部分是數據對象外,其他有很大一部分對象都是用來處理數據的。這些對象并不會經常發生變化,是系統中基礎的部分。在很多情況下,這些對象在系統中以單件的形式存在就可以滿足應用的需求,而且它們也不常涉及數據和狀態共享的問題。如果涉及數據共享方面的問題,需要在這些單件的基礎上做進一步的處理。
同時,這些對象之間的相互依賴關系也是比較穩定的,一般不會隨著應用的運行狀態的改變而改變。這些特性使得這些對象非常適合由IoC容器來管理,雖然它們存在于應用系統中,但是應用系統并不承擔管理這些對象的責任,而是通過依賴反轉把責任交給了容器(或者說平臺)。了解了這些背景,Spring IoC容器的原理也就不難理解了。在原理的具體實現上,Spring有著自己的獨特思路、實現技巧和豐富的產品特性。關于這些原理的實現,下面會進行詳細的分析。
第1章中,我們已經對建立本地源代碼環境做了簡要的介紹,該源代碼環境是我們分析Spring原理前要做的重要準備工作。同時,我們還需要針對IoC容器做一些額外的事情:根據Spring 3.0的源代碼組織特點,每個模塊作為獨立的Eclipse項目存在,所以現在需要在Eclipse中建立與IoC容器和上下文相關的代碼項目。這樣就可以方便地使用Eclipse的代碼分析工具來對相關模塊的實現進行分析。這個額外的準備過程在分析其他模塊時也是需要的,所以這里會做一個說明。
準備過程如圖2-1所示,打開Eclipse,依次選擇File→Import→General→Existing Projects into Workspace,然后再選擇org.springframework.beans和org.springframework.context兩個目錄,并將其導入到Eclipse本地環境中。這時即可看到在Package Explorer View中的Spring IoC容器的源代碼項目。

圖2-1 打開IoC容器的源代碼包
2.1.2 Spring的IoC容器系列
IoC容器為開發者管理對象之間的依賴關系提供了很多便利和基礎服務,有許多IoC容器供開發者選擇,SpringFramework的IoC核心就是其中的一個,它是開源的。那具體什么是IoC容器呢?它在Spring框架中到底長什么樣?其實對IoC容器的使用者來說,我們經常接觸到的BeanFactory和ApplicationContext都可以看成是容器的具體表現形式。我們通常所說的IoC容器,如果深入到Spring的實現去看,會發現IoC容器實際上代表著一系列功能各異的容器產品,只是容器的功能有大有小,有各自的特點。我們舉水桶為例子,在商店中出售的水桶有大有小,制作材料也各不相同,有金屬的、塑料的等,總之是各式各樣,但只要能裝水,具備水桶的基本特性,那就可以作為水桶來出售,來讓用戶使用。這在Spring中也是一樣,Spring有各式各樣的IoC容器的實現供用戶選擇和使用。使用什么樣的容器完全取決于用戶的需要,但在使用之前如果能夠了解容器的基本情況,那對容器的使用是非常有幫助的,就像我們在購買商品前對商品進行考察和挑選那樣。圖2-2展示了這個容器系列的概況。

圖2-2 Spring的IoC容器系列概況
就像商品需要有產品規格說明一樣,同樣,作為IoC容器,也需要為它的具體實現指定基本的功能規范,這個功能規范的設計表現為接口類BeanFactory,它體現了Spring為提供給用戶使用的IoC容器所設定的最基本功能規范。還是舉前面我們說的百貨商店出售的水桶為例子,如果把IoC容器看成一個水桶,那么這個BeanFactory就定義了可以作為水桶的基本功能,比如至少能裝水,有個提手什么的。滿足了基本的功能,為了不同場合的需要,水桶的生產廠家還在這個基礎上為用戶設計了其他各式各樣的水桶產品,來滿足不同的用戶需求。這些水桶會提供更豐富的功能,有簡約型的,有豪華型的,等等。但是,不管什么水桶,它都需要有一項最基本的功能:能夠裝水。那對Spring的具體IoC容器實現來說,它需要滿足的基本特性是什么呢?它需要滿足BeanFactory這個基本的接口定義,所以在圖2-2中可以看到,這個BeanFactory接口在繼承體系中的地位,它是作為一個最基本的接口類出現在Spring的IoC容器體系中的。
在這些Spring提供的基本IoC容器的接口定義和實現的基礎上,Spring通過定義BeanDefinition來管理基于Spring的應用中的各種對象以及它們之間的相互依賴關系。BeanDefinition抽象了我們對Bean的定義,是讓容器起作用的主要數據類型。我們都知道,在計算機的世界里,所有的功能都是建立在用數據對現實進行抽象的基礎上完成的。IoC容器是用來管理對象依賴關系的,對IoC容器來說,BeanDefinition就是對依賴反轉模式中管理的對象依賴關系的數據抽象,也是容器實現依賴反轉功能的核心數據結構,依賴反轉功能都是圍繞對這個BeanDefinition的處理上完成的。這些BeanDefinition就像是容器里裝的水,有了這些基本數據,容器才能夠發揮作用。在下面的分析中,BeanDefinition的上鏡次數會很多,我們在這里先簡單地打個招呼。
同時,在使用IoC容器時,了解BeanFactory和ApplicationContext之間的區別對我們理解和使用IoC容器也是比較重要的。弄清楚了這兩種重要容器之間的區別和聯系,意味著我們具備辨別容器系列中不同容器產品的能力。還有一個好處就是,如果需要定制特定功能的容器實現,也能比較方便地在容器系列中找到一款恰當的產品作為參考。
2.2 IoC容器系列的實現:BeanFactory和ApplicationContext
2.2.1 BeanFactory對IoC容器的功能定義
從前面的介紹,我們知道BeanFactory定義了IoC容器的基本功能規范,所以,下面我們就從BeanFactory這個最基本的容器定義來進入Spring的IoC容器體系,去了解IoC容器的實現原理。IoC容器的基本接口是由BeanFactory來定義的,也就是說,BeanFactory定義了IoC容器的最基本的形式,并且提供了IoC容器所應該遵守的最基本的服務契約。同時,這也是我們使用IoC容器所應遵守的最底層和最基本的編程規范,這些接口定義勾畫出了IoC的基本輪廓。很顯然,在Spring的代碼實現中,BeanFactory只是一個接口類,并沒有給出容器的具體實現,而我們在圖2-2中看到的各種具體類,比如DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等都可以看成是容器的附加了某種功能的具體實現,也就是容器體系中的具體容器產品。下面我們來看看BeanFactory是怎樣定義IoC容器的基本接口的。下面介紹這個基本接口為用戶提供的基本功能。
用戶使用容器時,可以使用轉義符“&”來得到FactoryBean本身,用來區分通過容器來獲取FactoryBean產生的對象和獲取FactoryBean本身。舉例來說,如果myJndiObject是一個FactoryBean,那么使用&myJndiObject得到的是FactoryBean,而不是myJndiObject這個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 { /** * Used to dereference a {@link FactoryBean} instance and distinguish it from * beans <i>created</i> by the FactoryBean. For example, if the bean named * <code>myJndiObject</code> is a FactoryBean, getting <code>&myJndiObject </code> * will return the factory, not the instance returned by the factory. */ String FACTORY_BEAN_PREFIX = "&"; /** * Return an instance, which may be shared or independent, of the specified bean. * <p>This method allows a Spring BeanFactory to be used as a replacement for the * Singleton or Prototype design pattern. Callers may retain references to * returned objects in the case of Singleton beans. * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. * */ Object getBean(String name) throws BeansException; /** * Return an instance, which may be shared or independent, of the specified bean. * <p>Behaves the same as {@link #getBean(String)}, but provides a measure of type * safety by throwing a BeanNotOfRequiredTypeException if the bean is not of the * required type. This means that ClassCastException can' t be thrown on casting * the result correctly, as can happen with {@link #getBean(String)}. * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. * */ <T> T getBean(String name, Class<T> requiredType) throws BeansException; /** * Return an instance, which may be shared or independent, of the specified bean. * <p>Allows for specifying explicit constructor arguments / factory method arguments, * overriding the specified default arguments (if any) in the bean definition. * */ Object getBean(String name, Object... args) throws BeansException; /** * Does this bean factory contain a bean with the given name? More specifically, * is {@link #getBean} able to obtain a bean instance for the given name? * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. */ boolean containsBean(String name); /** * Is this bean a shared singleton? That is, will {@link #getBean} always * return the same instance? * <p>Note: This method returning <code>false</code> does not clearly indicate * independent instances. It indicates non-singleton instances, which may correspond * to a scoped bean as well. Use the {@link #isPrototype} operation to explicitly * check for independent instances. * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. */ boolean isSingleton(String name) throws NoSuchBeanDefinitionException; /** * Is this bean a prototype? That is, will {@link #getBean} always return * independent instances? * <p>Note: This method returning <code>false</code> does not clearly indicate * a singleton object. It indicates non-independent instances, which may correspond * to a scoped bean as well. Use the {@link #isSingleton} operation to explicitly * check for a shared singleton instance. * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. */ boolean isPrototype(String name) throws NoSuchBeanDefinitionException; /** * Check whether the bean with the given name matches the specified type. * More specifically, check whether a {@link #getBean} call for the given name * would return an object that is assignable to the specified target type. * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. */ boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException; /** * Determine the type of the bean with the given name. More specifically, * determine the type of object that {@link #getBean} would return for the given name. * <p>For a {@link FactoryBean}, return the type of object that the FactoryBean creates, * as exposed by {@link FactoryBean#getObjectType()}. * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. */ Class getType(String name) throws NoSuchBeanDefinitionException; /** * Return the aliases for the given bean name, if any. * All of those aliases point to the same bean when used in a {@link #getBean} call. * <p>If the given name is an alias, the corresponding original bean name * and other aliases (if any) will be returned, with the original bean name * being the first element in the array. * <p>Will ask the parent factory if the bean cannot be found in this factory instance. */ String[] getAliases(String name); }
2.2.2 IoC容器XmlBeanFactory的工作原理
這個BeanFactory接口提供了使用IoC容器的規范。在這個基礎上,Spring還提供了符合這個IoC容器接口的一系列容器的實現供開發人員使用。例如,在圖2-2中,我們可以看到BeanFactory的相關部分的實現。為簡單起見,我們瀏覽一下圖2-2的BeanFactory的繼承體系,注意AutowireCapableBeanFactory→AbstractAutowireCapableBeanFactory→DefaultListableBeanFactory→XmlBeanFactory IoC容器的實現系列。
我們從這個容器系列的最底層實現XmlBeanFactory開始,這個容器的實現與我們在Spring應用中用到的那些上下文相比,有一個非常明顯的特點,它只提供了最基本的IoC容器的功能。從它的名字中可以看出,這個IoC容器可以讀取以XML形式定義的BeanDefinition。理解這一點有助于理解ApplicationContext與基本的BeanFactory之間的區別和聯系。我們可以認為直接的BeanFactory實現是IoC容器的基本形式,而各種ApplicationContext的實現是IoC容器的高級表現形式。關于ApplicationContext的分析,以及它與BeanFactory相比的增強特性都會在下面進行詳細的分析。
讓我們回顧一下這個繼承體系,從中可以清楚地看到它們之間的聯系,它們都是IoC容器系列的組成部分。在設計這個容器系列時,我們可以從繼承體系的發展上看到IoC容器各項功能的實現過程。如果要擴展自己的容器產品,建議讀者最好在這個繼承體系中檢驗一下,看看Spring是不是已經提供了現成的或相近的容器實現供我們參考。下面就從我們比較熟悉的XmlBeanFactory的實現入手進行分析,來看看一個基本的IoC容器是怎樣實現的。
如果仔細閱讀XmlBeanFactory的源碼,在一開始的注釋里面已經對XmlBeanFactory的功能做了簡要的說明,從代碼的注釋還可以看到,這是Rod Johnson在2001年就寫下的代碼,可見這個類應該是Spring的元老類了。它是繼承DefaultListableBeanFactory這個類的,而且它非常重要,在以后的分析中這個類會經常用到。我們會看到這個DefaultListableBeanFactory實際上包含了IoC容器的重要功能,也是在很多地方都會用到的容器系列中的一個基本產品。
從名字上就可以看出來,在Spring中,實際上是把它作為一個默認的完整功能的IoC容器來使用的。XmlBeanFactory在繼承了DefaultListableBeanFactory容器的功能的同時,給DefaultListableBeanFactory增加的功能很容易從XmlBeanFactory的名字上猜到。它是一個與XML相關的BeanFactory,也就是說它可以讀取以XML文件方式定義的BeanDefinition的一個IoC容器。
如果說XmlBeanFactory是一個可以讀取XML文件方式定義的BeanDefinition的IoC容器,那么這些實現XML讀取的功能是怎樣實現的呢?對這些XML文件定義信息的處理并不是由XmlBeanFactory來直接處理的。在XmlBeanFactory中,初始化了一個XmlBeanDefini-tionReader對象,有了這個Reader對象,那些以XML的方式定義的BeanDefinition就有了處理的地方。我們可以看到,對這些XML形式的信息的處理實際上是由這個XmlBeanDefini-tionReader來完成的。
構造XmlBeanFactory這個IoC容器時,需要指定BeanDefinition的信息來源,而這個信息來源需要封裝成Spring中的Resource類來給出。Resource是Spring用來封裝IO操作的類。比如,我們的BeanDefinition信息是以xml文件形式存在的,那么可以使用像ClassPathResource res = new ClassPathResource("beans.xml");這樣具體的ClassPathResource來構造需要的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作為基類,DefaultListableBeanFactory是很重要的一個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容器就可以直接使用了。
2.2.3 ApplicationContext的特點
我們了解了IoC容器建立的基本步驟。現在可以很方便地通過編程的方式來手工控制這些配置和容器的建立過程了。但是,在Spring中系統已經為用戶提供了許多已經定義好的容器實現,而不需要開發人員事必躬親。相比那些簡單拓展BeanFactory的基本IoC容器,開發人員常用的ApplicationContext除了能夠提供在上面看到的容器的基本功能外,還為用戶提供了以下的附加服務,可以讓客戶更方便地使用。所以說,ApplicationContext是一個高級形態意義的IoC容器,如圖2-3所示,可以看到ApplicationContext在BeanFactory的基礎上添加的附加功能,這些功能為ApplicationContext提供了以下BeanFactory不具備的新特性。

圖2-3 ApplicationContext的接口關系
? 支持不同的信息源。我們看到ApplicationContext擴展了MessageSource接口,這些信息源的擴展功能可以支持國際化的實現,為開發多語言版本的應用提供服務。
? 訪問資源。體現在對ResourceLoader和Resource的支持上,這樣我們可以從不同地方得到Bean定義資源。這種抽象使用戶程序可以靈活地定義Bean定義信息,尤其是從不同的IO途徑得到Bean定義信息。這在接口關系上看不出來,一般來說,具體Applic-ationContext都是繼承了DefaultResourceLoader的子類。因為DefaultResourc-eLoader是AbstractApplicationContext的基類,關于Resource在IoC容器中的使用,在2.3節中有詳細的講解。
? 支持應用事件。繼承了接口ApplicationEventPublisher,這樣在上下文中引入了事件機制。這些事件和Bean的生命周期的結合為Bean的管理提供了便利。
? 在ApplicationContext中提供的附加服務。這些服務使得基本IoC容器的功能更豐富。因為具備了這些豐富的附加功能,使得ApplicationContext與簡單的BeanFactory相比,對它的使用是一種面向框架的使用風格,所以一般建議在開發應用時使用ApplicationContext作為IoC容器的基本形式。
2.3 IoC容器的初始化
IoC容器的初始化包括BeanDefinition的Resouce定位、載入和注冊這三個基本的過程。在前面的編程式地使用DefaultListableBeanFactory中,我們可以看到定位和載入過程的接口調用。這里將詳細分析這三個過程的實現。值得注意的是,Spring在實現中是把這三個過程分開并使用不同的模塊來完成的,這樣可以讓用戶更加靈活地對這三個過程進行剪裁和擴展,定義出最適合自己的IoC容器的初始化過程。
BeanDefinition的資源定位由ResourceLoader通過統一的Resource接口來完成,這個Resource對各種形式的BeanDefinition的使用提供了統一接口。對于這些BeanDefinition的存在形式,相信大家都不會感到陌生。比如說,在文件系統中的Bean定義信息可以使用FileSystemResource來進行抽象;在類路徑中可以使用前面提到的ClassPathResource來使用,等等。這個過程類似于容器尋找數據的過程,就像用水桶裝水先要把水找到一樣。
第二個關鍵的部分是BeanDefinition的載入,該載入過程把用戶定義好的Bean表示成IoC容器內部的數據結構,而這個容器內部的數據結構就是BeanDefinition,下面可以看到這個數據結構的詳細定義。總地說來,這個BeanDefinition實際上就是POJO對象在IoC容器中的抽象,這個BeanDefinition定義了一系列的數據來使得IoC容器能夠方便地對POJO對象也就是Spring的Bean進行管理。即BeanDefinition就是Spring的領域對象。下面我們會對這個載入的過程進行詳細的分析,便于大家對整個過程有比較清楚的了解。
第三個過程是向IoC容器注冊這些BeanDefinition的過程。這個過程是通過調用BeanDefinitionRegistry接口的實現來完成的,這個注冊過程把載入過程中解析得到的BeanDefinition向IoC容器進行注冊。可以看到,在IoC容器內部,是通過使用一個HashMap來持有這些BeanDefinition數據的。
值得注意的是,IoC容器和上下文的初始化一般不包含Bean依賴注入的實現。一般而言,依賴注入發生在應用第一次向容器通過getBean索取Bean時。但有一個例外值得注意,在使用IoC容器時有一個預實例化的配置,這個預實例化是可以配置的,具體來說可以通過在Bean定義信息中的lazyinit屬性來設定;有了這個預實例化的特性,用戶可以對容器初始化過程作一個微小的控制;從而改變這個被設置了lazyinit屬性的Bean的依賴注入的發生,使得這個Bean的依賴注入在IoC容器初始化時就預先完成了。有了以上的一個大概的輪廓,下面就詳細地看一看在IoC容器的初始化過程中,BeanDefinition的資源定位、載入和解析過程是怎么實現的。
2.3.1 BeanDefinition的Resource定位
以編程的方式使用DefaultListableBeanFactory時,我們可以看到,首先定義一個Resource來定位容器使用的BeanDefinition。這時使用的是ClassPathResource,意味著Spring會在類路徑中尋找以文件形式存在的BeanDefinition信息。
ClassPathResource res = new ClassPathResource("beans.xml");
這個定義的Resource并不能讓DefaultListableBeanFactory直接使用,Spring是通過BeanDefinitionReader來對這些信息進行處理的。在這里,我們也可以看到使用ApplicationContext相對于直接使用DefaultListableBeanFactory的好處。因為在ApplicationContext中,Spring已經為我們提供了一系列加載不同Resource的讀取器的實現,而DefaultListableBeanFactory只是一個純粹的IoC容器,需要為它配置特定的讀取器才能完成這些功能。當然,有利就有弊,使用DefaultListableBeanFactory這種更底層的容器,卻能提高我們定制IoC容器的靈活性。
回到我們經常使用的ApplicationContext上來,例如FileSystemXmlApplication-Context、ClassPathXmlApplicationContext以及XmlWebApplicationContext等。簡單地從這些類的名字上分析,可以清楚地看到它們可以提供哪些不同的Resource讀入功能,比如FileSystemXmlApplicationContext可以從文件系統載入Resource, ClassPathXm-lApplicationContext可以從Class Path載入Resource, XmlWebApplicationContext可以在Web容器中載入Resource,等等。
下面以FileSystemXmlApplicationContext為例,通過分析這個ApplicationContext的實現來看看它是怎樣完成這個Resource定位過程的。作為輔助,我們可以在圖2-4中看到相應的ApplicationContext繼承體系。

圖2-4 FileSystemXmlApplicationContext的繼承關系
從圖2-4中可以看到,這個FileSystemXmlApplicationContext已經通過繼承Abstra-ctApplicationContext具備了ResourceLoader讀入以Resource定義的BeanDefiniti-on的能力,因為AbstractApplicationContext的基類是DefaultResourceLoader。下面看看FileSystemXmlApplicationContext的具體實現,如代碼清單2-4所示。
代碼清單2-4 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的文件路徑的同時, *還允許指定自己的雙親IoC容器。 */ public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { this(configLocations, true, parent); } 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的loadBeanDefintion中被調用的。 *loadBeanDefintion采用了模板模式,具體的定位實現實際上是由各個子類完成的。 */ protected Resource getResourceByPath(String path) { if (path ! = null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); } }
在FileSystemApplicationContext中,我們可以看到實現了兩個部分的功能,一部分是在構造函數中,對configuration進行處理,使得所有在配置在文件系統中的XML文件方式的BeanDefnition都能夠得到有效的處理,比如實現了getResourceByPath方法,這個方法是一個模板方法,是為讀取Resource服務的。對于IoC容器功能的實現,這里沒有涉及,因為它繼承了AbstractXmlApplicationContext,關于IoC容器功能相關的實現,都是在FileSystemXmlApplicationContext中完成的,但是在構造函數中通過refresh來啟動了IoC容器的初始化,這個refresh方法非常重要,也是我們以后分析容器初始化過程實現的一個重要入口。
注意 FileSystemApplicationContext是一個支持XML定義BeanDefinition的ApplicationContext,并且可以指定以文件形式的BeanDefinition的讀入,這些文件可以使用文件路徑和URL定義來表示。在測試環境和獨立應用環境中,這個ApplicationContext是非常的有用的。
根據圖2-5的調用關系分析,我們可以清楚地看到整個BeanDefinition資源定位的過程。這個對BeanDefinition資源定位的過程,最初是由refresh來觸發的,這個refresh的調用是在FileSystemXmlBeanFactory的構造函數中啟動的。

圖2-5 getResourceByPath的調用關系
大家看了上面的調用過程可能會比較好奇,這個FileSystemXmlApplicationContext在什么地方定義了BeanDefinition的讀入器BeanDefinitionReader,從而完成BeanDefi-nition信息的讀入呢?在前面分析過,在IoC容器的初始化過程中,BeanDefinition資源的定位、讀入和注冊過程是分開進行的,這也是解耦的一個體現。關于這個讀入器的配置,可以到FileSystemXmlApplicationContext的基類AbstractRefreshableApplicationContext中看看它是怎樣實現的。
我們重點看看AbstractRefreshableApplicationContext的refreshBeanFactory方法的實現,這個refreshBeanFactory被FileSystemXmlApplicationContext構造函數中的refresh調用。在這個方法里,通過createBeanFactroy構建了一個IoC容器供Appl-icationContext使用。這個IoC容器就是我們前面提到過的DefaultListableBeanFactory,同時,它啟動了loadBeanDefinitions來載入BeanDefinition,這個過程和我們前面看到的編程式的使用IoC容器(XmlBeanFactory)的過程非常類似。
從代碼清單2-4中可以看到,在初始化FileSystmXmlApplicationContext的過程中,通過IoC容器的初始化的refresh來啟動整個調用,使用的IoC容器是DefultListableBean Factory。具體的資源載入在XmlBeanDefinitionReader讀入BeanDefinition時完成,在XmlBeanDefinitionReader的基類AbstractBeanDefinitionReader中可以看到這個載入過程的具體實現。對載入過程的啟動,可以在AbstractRefreshableApplicationCont-ext的loadBeanDefinitions方法中看到,如代碼清單2-5所示。
代碼清單2-5 AbstractRefreshableApplicationContext對容器的初始化
protected final void refreshBeanFactory() throws BeansException { //這里判斷,如果已經建立了BeanFactory,則銷毀并關閉該BeanFactory。 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } //這里是創建并設置持有的DefaultListableBeanFactor的地方。 //同時調用loadBeanDefinitions再載入BeanDefinitione的信息。 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 XML document for " + getDisplayName(), ex); } } /** *這就是在上下文中創建DefaultListableBeanFactory的地方,而getInternalParentBeanFactory()的 *具體實現可以參看AbstractApplicationContext中的實現,會根據容器已有的雙親IoC容器的信息來 *生成DefaultListableBeanFactory的雙親IoC容器。 */ protected DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(getInternalParentBeanFactory()); } /** *這里是使用BeanDefinitionReader載入Bean定義的地方,因為允許有多種載入方式, *雖然用得最多的是XML定義的形式,這里通過一個抽象函數把具體的實現委托給子類來完成。 */ protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException, BeansException; public int loadBeanDefinitions(String location, Set 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 { // 調用DefaultResourceLoader的getResource完成具體的Resource定位。 Resource[] resources = ((ResourcePatternResolver) resourceLoader). getResources(location); int loadCount = loadBeanDefinitions(resources); if (actualResources ! = null) { for (int i = 0; i < resources.length; i++) { actualResources.add(resources[i]); } } 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 { // Can only load single resources by absolute URL. // 調用DefaultResourceLoader的getResource完成具體的Resource定位。 Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource); if (actualResources ! = null) { actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } } //對于取得Resource的具體過程,我們可以看看DefaultResourceLoader是怎樣完成的: public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); //這里處理帶有classpath標識的Resource。 if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX. length()), getClassLoader()); } else { try { // Try to parse the location as a URL... // 這里處理URL標識的Resource定位。 URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. /** *如果既不是classpath,也不是URL標識的Resource定位,則把getResource的重任 *交給getResourceByPath,這個方法是一個protected方法,默認的實現是得到一 *個ClassPathContextResource,這個方法常常會用子類來實現。 */ return getResourceByPath(location); } } }
前面我們看到的getResourceByPath會被子類FileSystemXmlApplicationContext實現,這個方法返回的是一個FileSystemResource對象,通過這個對象Spring可以進行相關的IO操作,完成BeanDefinition的定位。分析到這里已經一目了然,它實現的就是對path進行解析,然后生成一個FileSystemResource對象并返回,如代碼清單2-6所示。
代碼清單2-6 FileSystemXmlApplicationContext生成Resource對象
protected Resource getResourceByPath(String path) { if (path ! = null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
如果是其他的ApplicationContext,那么對應地會生成其他種類的Resource,比如ClassPathResource、ServletContextResource等。關于Spring中Resource的種類,可以在圖2-6中的Resource類的繼承關系中了解。作為接口的Resource定義了許多與IO相關的操作,這些操作也都可以從圖2-6中Resource的接口定義中看到。這些接口對不同的Resource實現代表著不同的意義,是Resource的實現需要考慮的。

圖2-6 Resource的定義和繼承關系
從圖2-6中我們可以看到Resource的定義和它的繼承關系,通過對前面的實現原理的分析,我們以FileSystemXmlApplicationContext的實現原理為例子,了解了Resource定位問題的解決方案,即以FileSystem方式存在的Resource的定位實現。在BeanDefinition定位完成的基礎上,就可以通過返回的Resource對象來進行BeanDefinition的載入了。在定位過程完成以后,為BeanDefinition的載入創造了IO操作的條件,但是具體的數據還沒有開始讀入。這些數據的讀入將在下面看到的BeanDefinition的載入和解析中來完成。仍然以水桶為例子,這里就像如果要用水桶去打水,那么先要找到水源。這里完成對Resource的定位,就類似于水源已經找到了,下面就是打水的過程了,類似于把找到的水裝到水桶里的過程。找水不簡單,但與打水相比,我們發現打水更需要技巧。
2.3.2 BeanDefinition的載入和解析
對IoC容器來說,BeanDefinition的載入過程相當于把我們定義的BeanDefinition在IoC容器中轉化成一個Spring內部表示的數據結構的過程。IoC容器對Bean的管理和依賴注入功能的實現,是通過對其持有的BeanDefinition進行各種相關的操作來完成的。這些BeanDefinition數據在IoC容器里通過一個HashMap來保持和維護,當然這只是一種比較簡單的維護方式,如果你覺得需要提高IoC容器的性能和容量,完全可以自己做一些擴展。我們從DefaultListableBeanFactory來入手看看IoC容器是怎樣完成BeanDefinition載入的。這個DefaultListableBeanFactory已經是我們非常熟悉的基本IoC容器,在前面已經碰到過多次,相信大家對它一定不會感到陌生。為了了解這一點,我們先回到IoC容器的初始化入口,也就是到refresh()方法去看一看。這個方法的最初是在FileSystemXmlApplicationContext的構造函數中被調用的,它的調用意味著容器的初始化或數據更新,這些初始化和更新的數據當然就是BeanDefinition,如代碼清單2-7所示。
代碼清單2-7啟動BeanDefinition的載入
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); //這里調用容器的refresh,是載入BeanDefinition的入口。 if (refresh) { refresh(); } }
對于容器的啟動來說,refresh是一個很重要的方法,我們看看它的實現。在AbstractApplicationContext類(它是FileSystemXmlApplicationContext的基類)中找到這個方法,它詳細地描述了整個ApplicationContext的初始化過程,比如BeanFactory的更新,messagesource和postprocessor的注冊,等等。這里看起來更像是對ApplicationContext進行初始化的模板或執行提綱,這個執行過程為IoC容器Bean的生命周期管理提供了條件。這個IoC容器的refresh過程如代碼清單2-8所示。
代碼清單2-8對IoC容器的refresh的實現
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. // 這里是在子類中啟動refreshBeanFactory()的地方。 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) { // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset ' active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
我們進入到AbstractRefreshableApplicationContext的refreshBeanFactory()方法中,在這個方法里創建了BeanFactory。在創建IoC容器前,如果已經有容器存在,那么需要把已有的容器銷毀和關閉,保證在refresh以后使用的是新建立起來的IoC容器。這么看來,這個refresh非常像我們對容器的重啟動,就像計算機的重啟動那樣。在建立好當前的IoC容器以后,開始了對容器的初始化過程,比如BeanDefinition的載入,具體的實現如代碼清單2-9所示。
代碼清單2-9 AbstractRefreshableApplicationContext的refreshBeanFactory方法
protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //創建IoC容器,這里使用的是DefaultListableBeanFactory。 DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); //啟動對BeanDefintion的載入。 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing XML document for " + getDisplayName(), ex); } }
這里調用的loadBeanDefinitions實際上是一個抽象方法,那么實際的載入過程是在哪里發生的呢?我們看看前面提到的loadBeanDefinitios在AbstractRefreshableApplicat ionContext的子類AbstractXmlApplicationContext中的實現,在這個loadBeanDefin itions中,初始化了讀取器XmlBeanDefinitionReader,然后再把這個讀取器在IoC容器中設置好(過程和編程式使用XmlBeanFactory是類似的),最后是啟動讀入器來完成BeanDefin ition在IoC容器中的載入,如代碼清單2-10所示。
代碼清單2-10 AbstractXmlApplicationContext中的loadBeanDefinitions
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext { public AbstractXmlApplicationContext() { } public AbstractXmlApplicationContext(ApplicationContext parent) { super(parent); } //這里是實現loadBeanDefinitions的地方。 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. /** *創建XmlBeanDefinitionReader,并通過回調設置到BeanFactory中去, *創建BeanFactory的過程可以參考上文對編程式使用IoC容器的相關分析,這里和前面一樣, *使用的也是DefaultListableBeanFactory。 */ XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader (beanFactory); /** *Configure the bean definition reader with this context' s *resource loading environment. */ /** *這里設置XmlBeanDefinitionReader,為XmlBeanDefinitionReader *配置ResourceLoader,因為DefaultResourceLoader是父類,所以this可以直接被使用。 */ 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. */ // 這是啟動Bean定義信息載入的過程。 initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); } protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) { }
接著就是loadBeanDefinitions調用的地方,首先得到BeanDefinition信息的Resource定位,然后直接調用XmlBeanDefinitionReader讀取,具體的載入過程是委托給BeanDefinitionReader完成的。因為這里的BeanDefinition是通過XML文件定義的,所以這里使用XmlBeanDefinitionReader來載入BeanDefinition到容器中,如代碼清單2-11所示。
代碼清單2-11 XmlBeanDefinitionReader載入XmlBeanDefinitionReader
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources ! = null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations ! = null) { reader.loadBeanDefinitions(configLocations); } } protected Resource[] getConfigResources() { return null; } }
通過以上實現原理的分析,我們可以看到,在初始化FileSystmXmlApplicationContext的過程中,是通過調用IoC容器的refresh來啟動整個BeanDefinition的載入過程的,這個初始化是通過定義的XmlBeanDefinitionReader來完成的。同時,我們也知道實際使用的IoC容器是DefultListableBeanFactory,具體的Resource載入在XmlBeanDefinitionReader讀入BeanDefinition時實現。因為Spring可以對應不同形式的BeanDefinition。由于這里使用的是XML方式的定義,所以需要使用XmlBeanDefinitionReader。如果使用了其他的BeanDefinition方式,就需要使用其他種類的BeanDefinitionReader來完成數據的載入工作。在XmlBeanDefinitionReader的實現中可以看到,是在reader.loadBeanDefinitions中開始進行BeanDefinition的載入的,而這時XmlBeanDefinitionReader的父類AbstractBeanDefinitionReader已經為BeanDefinition的載入做好了準備,如代碼清單2-12所示。
代碼清單2-12 AbstractBeanDefinitionReader載入BeanDefinitions
public int loadBeanDefinitions(Resource[] resources) throws BeanDefinitionStoreException { //如果Resource為空,則停止BeanDefinition的載入。 /** *然后啟動載入BeanDefinition的過程,這個過程會遍歷整個Resource集合所包含的 *BeanDefinition信息。 */ Assert.notNull(resources, "Resource array must not be null"); int counter = 0; for (int i = 0; i < resources.length; i++) { counter += loadBeanDefinitions(resources[i]); } return counter; }
這里調用的是loadBeanDefinitions(Resource res)方法,然而這個方法在AbstractBeanDefinitionReader類里是沒有實現的,它是一個接口方法,具體的實現在XmlBeanDefinitionReader中。在讀取器中,需要得到代表XML文件的Resource,因為這個Resource對象封裝了對XML文件的IO操作,所以讀取器可以在打開IO流后得到XML的文件對象。有了這個Document對象以后,就可以按照Spring的Bean定義規則來對這個XML的文檔樹進行解析了,這個解析是交給BeanDefinitionParserDelegate來完成的,看起來實現脈絡很清楚。具體可以參考代碼實現,如代碼清單2-13所示。
代碼清單2-13對BeanDefinition的載入實現
//這里是調用的入口。 public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); } //這里是載入XML形式的BeanDefinition的地方。 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 recursive loading of " + encodedResource + " - check your import definitions! "); } //這里得到XML文件,并得到IO的InputSource準備進行讀取。 try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() ! = null) { inputSource.setEncoding(encodedResource.getEncoding()); } 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.set(null); } } } //具體的讀取過程可以在doLoadBeanDefinitions方法中找到。 //這是從特定的XML文件中實際載入BeanDefinition的地方。 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { int validationMode = getValidationModeForResource(resource); /** *這里取得XML文件的Document對象,這個解析過程是由documentLoader完成的, *這個documentLoader是DefaultDocumentLoader,在定義documentLoader的地方創建。 */ Document doc = this.documentLoader.loadDocument( inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware()); /** *這里啟動的是對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); } }
感興趣的讀者,可以到DefaultDocumentLoader中去看看是怎樣得到Document對象的,這里就不詳細分析了。我們關心的是Spring的BeanDefinion是怎樣按照Spring的Bean語義要求進行解析并轉化為容器內部數據結構的,這個過程是在registerBeanDefinitions (doc, resource)中完成的。具體的過程是由BeanDefinitionDocumentReader來完成的,這個registerBeanDefinition還對載入的Bean的數量進行了統計。具體的過程如代碼清單2-14所示。
代碼清單2-14 registerBeanDefinition的代碼實現
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // Read document based on new BeanDefinitionDocumentReader SPI. // 這里得到BeanDefinitionDocumentReader來對xml的BeanDefinition進行解析。 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); // 具體的解析過程在這個registerBeanDefinitions中完成。 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
BeanDefinition的載入包括兩部分,首先是通過調用XML的解析器得到document對象,但這些document對象并沒有按照Spring的Bean規則進行解析。在完成通用的XML解析以后,才是按照Spring的Bean規則進行解析的地方,按照Spring的Bean規則進行解析的過程是在documentReader中實現的。這里使用的documentReader是默認設置好的DefaultBeanDe-finitionDocumentReader。這個DefaultBeanDefinitionDocumentReader的創建是在以下的方法里完成的,然后再完成BeanDefinition的處理,處理的結果由BeanDefinition-Holder對象來持有。這個BeanDefinitionHolder除了持有BeanDefinition對象外,還持有了其他與BeanDefinition的使用相關的信息,比如Bean的名字、別名集合等。這個Bean-DefinitionHolder的生成是通過對Document文檔樹的內容進行解析來完成的,可以看到這個解析過程是由BeanDefinitionParserDelegate來實現(具體在processBeanDefinition方法中實現)的,同時這個解析是與Spring對BeanDefinition的配置規則緊密相關的。具體的實現原理如代碼清單2-15所示。
代碼清單2-15創建BeanDefinitionDocumentReader
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass (this.documentReaderClass)); } //這樣,得到了documentReader以后,為具體的Spring Bean的解析過程準備好了數據。 /** *這里是處理BeanDefinition的地方,具體的處理是委托給BeanDefinitionParserDelegate *來完成的,ele對應于我們的Spring BeanDefinition中定義的xml元素。 */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { /* BeanDefinitionHolder是BeanDefinition對象的封裝類,封裝了BeanDefinition, *Bean的名字和別名。用它來完成向IoC容器注冊。得到這個BeanDefinitionHolder實際上就意 *味著獲得了BeanDefinition,是通過BeanDefinitionParserDelegate對XML元素的信息按照 *Spring的Bean規則進行解析得到的。 */ BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder ! = null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // 這里是向IoC容器注冊解析得到的BeanDefinition的地方。 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name ' " + bdHolder.getBeanName() + "' ", ele, ex); } // 在BeanDefinition向IoC容器注冊完以后,發送消息。 getReaderContext().fireComponentRegistered(new BeanComponentDefinition (bdHolder)); } }
具體的Spring BeanDefinition的解析是在BeanDefinitionParserDelegate中完成的。這個類里包含了各種Spring Bean定義規則的處理,感興趣的讀者可以仔細研究。比如我們最熟悉的對Bean元素的處理是怎樣完成的,也就是在XML定義文件中出現的<bean></bean>這個最常見的元素信息是怎樣處理的。在這里,我們會看到那些熟悉的BeanDefinition定義的處理,比如id、name、aliase等屬性元素。把這些元素的值從XML文件相應的元素的屬性中讀取出來以后,會被設置到生成的BeanDefinitionHolder中去。這些屬性的解析還是比較簡單的。對于其他元素配置的解析,比如各種Bean的屬性配置,通過一個較為復雜的解析過程,這個過程是由parseBeanDefinitionElement來完成的。解析完成以后,會把解析結果放到BeanDefinition對象中并設置到BeanDefinitionHolder中去,如代碼清單2-16所示。
代碼清單2-16 BeanDefinitionParserDelegate對bean元素定義的處理代碼清單2-17對BeanDefinition定義元素的處理
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { //這里取得在<bean>元素中定義的id、name和aliase屬性的值。 String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<String>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_ DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if (! StringUtils.hasText(beanName) && ! aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug("No XML ' id' specified - using ' " + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } //這個方法會引發對bean元素的詳細解析。 AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition ! = null) { if (! StringUtils.hasText(beanName)) { try { if (containingBean ! = null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); /** *Register an alias for the plain bean class name, if still *possible, if the generator returned the class name plus a *suffix.This is expected for Spring 1.2/2.0 backwards *compatibility. */ String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName ! = null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug("Neither XML ' id' nor ' name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
我們看到了對Bean元素進行解析的過程,也就是BeanDefinition依據XML的<bean>定義被創建的過程。這個BeanDefinition可以看成是<bean>定義的抽象,如圖2-7所示。這個數據對象里封裝的數據大多都是與<bean>定義相關的,也有很多就是我們在定義Bean時看到的那些Spring標記,比如我們熟悉的init-method、destroy-method、factory-method,等等,這個BeanDefinition數據類型是非常重要的,它封裝了很多基本數據。有了這些基本數據,IoC容器才能對Bean配置進行處理,才能實現相應的容器特性。

圖2-7 BeanDefinition的數據定義
看起來很熟悉吧,beanClass、description、lazyInit這些屬性都是在配置Bean時經常碰到的,原來都跑到這里來了。這個BeanDefinition是IoC容器體系中非常重要的核心數據結構。通過解析以后,這些數據已經做好在IoC容器里大顯身手的準備了。對BeanDefinition的元素的處理如代碼清單2-17所示,在這個過程中可以看到對Bean定義的相關處理,比如對元素attribute值的處理,對元素屬性值的處理,對構造函數設置的處理,等等。
public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); /** *這里只讀取定義的<bean>中設置的class名字,然后載入到BeanDefinition中去, *只是做個記錄,并不涉及對象的實例化過程,對象的實例化實際上是在依賴注入時完成的。 */ String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } try { String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } //這里生成需要的BeanDefinition對象,為Bean定義信息的載入做準備。 AbstractBeanDefinition bd = createBeanDefinition(className, parent); //這里對當前的Bean元素進行屬性解析,并設置description的信息。 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); //從名字可以清楚地看到,這里是對各種<bean>元素的信息進行解析的地方。 parseMetaElements(ele, bd); parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); //解析<bean>的構造函數設置。 parseConstructorArgElements(ele, bd); //解析<bean>的property設置。 parsePropertyElements(ele, bd); parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } /** *下面這些異常是我們在配置bean出現問題時經常可以看到的,原來是在這里拋出的,這些檢查是在 *createBeanDefinition時進行的,會檢查bean的class設置是否正確,比如這個類是不是能找到。 */ catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); } return null; }
上面是具體生成BeanDefinition的地方。在這里,我們舉一個對property進行解析的例子來完成對整個BeanDefinition載入過程的分析,還是在類BeanDefinitionParserDelegate的代碼中,它對BeanDefinition中的定義一層一層地進行解析,比如從屬性元素集合到具體的每一個屬性元素,然后才是對具體的屬性值的處理。根據解析結果,對這些屬性值的處理會封裝成PropertyValue對象并設置到BeanDefinition對象中去,如代碼清單2-18所示。
代碼清單2-18對BeanDefinition中Property元素集合的處理
//這里對指定bean元素的property子元素集合進行解析。 public void parsePropertyElements(Element beanEle, BeanDefinition bd) { //遍歷所有bean元素下定義的property元素。 NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element && DomUtils.nodeNameEquals(node, PROPERTY_ ELEMENT)) { //在判斷是property元素后對該property元素進行解析的過程。 parsePropertyElement((Element) node, bd); } } } public void parsePropertyElement(Element ele, BeanDefinition bd) { //這里取得property的名字。 String propertyName = ele.getAttribute(NAME_ATTRIBUTE); if (! StringUtils.hasLength(propertyName)) { error("Tag ' property' must have a ' name' attribute", ele); return; } this.parseState.push(new PropertyEntry(propertyName)); try { /** *如果同一個bean中已經有同名property的存在,則不進行解析,直接返回。也就是說, *如果在同一個bean中有同名的property設置,那么起作用的只是第一個。 */ if (bd.getPropertyValues().contains(propertyName)) { error("Multiple ' property' definitions for property ' " + propertyName + "' ", ele); return; } /** *這里是解析property值的地方,返回的對象對應對Bean定義的property屬性 *設置的解析結果,這個解析結果會封裝到PropertyValue對象中,然后設置到 *BeanDefinitionHolder中去。 */ Object val = parsePropertyValue(ele, bd, propertyName); PropertyValue pv = new PropertyValue(propertyName, val); parseMetaElements(ele, pv); pv.setSource(extractSource(ele)); bd.getPropertyValues().addPropertyValue(pv); } finally { this.parseState.pop(); } } //這里取得property元素的值,也許是一個list或其他。 public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) { String elementName = (propertyName ! = null) ? "<property> element for property ' " + propertyName + "' " : "<constructor-arg> element"; // Should only have one child element: ref, value, list, etc. NodeList nl = ele.getChildNodes(); Element subElement = null; for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element && ! DomUtils.nodeNameEquals(node, DESCRIPTION_ ELEMENT) && !DomUtils.nodeNameEquals(node, META_ELEMENT)) { // Child element is what we' re looking for. if (subElement ! = null) { error(elementName + " must not contain more than one sub-element", ele); } else { subElement = (Element) node; } } } //這里判斷property的屬性,是ref還是value,不允許同時是ref和value。 boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); if ((hasRefAttribute && hasValueAttribute) —— ((hasRefAttribute —— hasValueAttribute) && subElement ! = null)) { error(elementName + " is only allowed to contain either ' ref' attribute OR ' value' attribute OR sub-element", ele); } //如果是ref,創建一個ref的數據對象RuntimeBeanReference,這個對象封裝了ref的信息。 if (hasRefAttribute) { String refName = ele.getAttribute(REF_ATTRIBUTE); if (! StringUtils.hasText(refName)) { error(elementName + " contains empty ' ref' attribute", ele); } RuntimeBeanReference ref = new RuntimeBeanReference(refName); ref.setSource(extractSource(ele)); return ref; } //如果是value,創建一個它的數據對象TypedStringValue,這個對象封裝了value的信息。 else if (hasValueAttribute) { TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute (VALUE_ATTRIBUTE)); valueHolder.setSource(extractSource(ele)); return valueHolder; } //如果還有子元素,觸發對子元素的解析。 else if (subElement ! = null) { return parsePropertySubElement(subElement, bd); } else { // Neither child element nor "ref" or "value" attribute found. error(elementName + " must specify a ref or value", ele); return null; } }
這里是對property子元素的解析過程,Array、List、Set、Map、Prop等各種元素都會在這里進行解析,生成對應的數據對象,比如ManagedList、ManagedArray、ManagedSet等。這些Managed類是Spring對具體的BeanDefinition的數據封裝。具體的解析過程讀者可以去看看自己感興趣的部分,比如parseArrayElement、parseListElement、parseSetElement、parseMapElement、parsePropElement對應著不同類型的數據解析,同時這些具體的解析方法在BeanDefinitionParserDelegate類中也都能夠找到。因為方法命名很清晰,所以從方法名字上就能夠很快地找到。下面,以對Property的元素進行解析的過程為例,通過它的實現來說明這個具體的解析過程是怎樣完成的,如代碼清單2-19所示。
代碼清單2-19對屬性元素進行解析
public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) { if (! isDefaultNamespace(ele.getNamespaceURI())) { return parseNestedCustomElement(ele, bd); } else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) { BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd); if (nestedBd ! = null) { nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd); } return nestedBd; } else if (DomUtils.nodeNameEquals(ele, REF_ELEMENT)) { // A generic reference to any name of any bean. String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); boolean toParent = false; if (! StringUtils.hasLength(refName)) { // A reference to the id of another bean in the same XML file. refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE); if (! StringUtils.hasLength(refName)) { // A reference to the id of another bean in a parent context. refName = ele.getAttribute(PARENT_REF_ATTRIBUTE); toParent = true; if (! StringUtils.hasLength(refName)) { error("' bean' , ' local' or ' parent' is required for <ref> element", ele); return null; } } } if (! StringUtils.hasText(refName)) { error("<ref> element contains empty target attribute", ele); return null; } RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent); ref.setSource(extractSource(ele)); return ref; } else if (DomUtils.nodeNameEquals(ele, IDREF_ELEMENT)) { return parseIdRefElement(ele); } else if (DomUtils.nodeNameEquals(ele, VALUE_ELEMENT)) { return parseValueElement(ele, defaultValueType); } else if (DomUtils.nodeNameEquals(ele, NULL_ELEMENT)) { // It' s a distinguished null value. Let' s wrap it in a TypedStringValue // object in order to preserve the source location. TypedStringValue nullHolder = new TypedStringValue(null); nullHolder.setSource(extractSource(ele)); return nullHolder; } else if (DomUtils.nodeNameEquals(ele, ARRAY_ELEMENT)) { return parseArrayElement(ele, bd); } else if (DomUtils.nodeNameEquals(ele, LIST_ELEMENT)) { return parseListElement(ele, bd); } else if (DomUtils.nodeNameEquals(ele, SET_ELEMENT)) { return parseSetElement(ele, bd); } else if (DomUtils.nodeNameEquals(ele, MAP_ELEMENT)) { return parseMapElement(ele, bd); } else if (DomUtils.nodeNameEquals(ele, PROPS_ELEMENT)) { return parsePropsElement(ele); } else { error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele); return null; } }
我們看看類似List這樣的屬性配置是怎樣被解析的,依然在BeanDefinitionParser-Delegate中,返回的是一個List對象,這個List是Spring定義的ManagedList,作為封裝List這類配置定義的數據封裝,如代碼清單2-20所示。
代碼清單2-20解析BeanDefinition中的List元素
public List parseListElement(Element collectionEle, BeanDefinition bd) { String defaultElementType = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE); NodeList nl = collectionEle.getChildNodes(); ManagedList<Object> target = new ManagedList<Object>(nl.getLength()); target.setSource(extractSource(collectionEle)); target.setElementTypeName(defaultElementType); target.setMergeEnabled(parseMergeAttribute(collectionEle)); //具體的List元素的解析過程。 parseCollectionElements(nl, target, bd, defaultElementType); return target; } protected void parseCollectionElements( NodeList elementNodes, Collection<Object> target, BeanDefinition bd, String defaultElementType) { //遍歷所有的元素節點,并判斷其類型是否為Element。 for (int i = 0; i < elementNodes.getLength(); i++) { Node node = elementNodes.item(i); if (node instanceof Element && ! DomUtils.nodeNameEquals(node, DESCRIPTION_ ELEMENT)) { /** *加入到target中,target是一個ManagedList,同時觸發對下一層子元素的解析過程, *這是一個遞歸調用。 */ target.add(parsePropertySubElement((Element) node, bd, defaultElement Type)); } } }
經過這樣逐層地解析,我們在XML文件中定義的BeanDefinition就被整個給載入到了IoC容器中,并在容器中建立了數據映射。在IoC容器中建立了對應的數據結構,或者說可以看成是POJO對象在IoC容器中的映像,這些數據結構可以以AbstractBeanDefinition為入口,讓IoC容器執行索引、查詢和操作。簡單的POJO操作背后其實并不簡單,經過以上的載入過程,IoC容器大致完成了管理Bean對象的數據準備工作(或者說是初始化過程)。但是,重要的依賴注入實際上在這個時候還沒有發生,現在,在IoC容器BeanDefinition中存在的還只是一些靜態的配置信息。嚴格地來說,這時候的容器還沒有完全起作用,要完全發揮容器的作用,還需完成數據向容器的注冊。
2.3.3 BeanDefinition在IoC容器中的注冊
我們已經分析過BeanDefinition在IoC容器中載入和解析的過程。在這些動作完成以后,用戶定義的BeanDefinition信息已經在IoC容器內建立起了自己的數據結構以及相應的數據表示,但此時這些數據還不能讓IoC容器直接使用,需要在IoC容器中對這些BeanDefinition數據進行注冊。這個注冊為IoC容器了提供更友好的使用方式,在DefaultListableBeanFactory中,是通過一個HashMap來持有載入的BeanDefinition的,這個HashMap的定義在DefaultListableBeanFactory可以看到,如下所示。
/** Map of bean definition objects, keyed by bean name */ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap <String, BeanDefinition>();
將解析得到的BeanDefinition向IoC容器中的beanDefinitionMap注冊的過程是在載入BeanDefinition完成后進行的,注冊的調用過程如圖2-8所示。

圖2-8 registerBeanDefinition的調用過程
我們跟蹤以上的代碼調用去看一下具體的注冊實現,在DefaultListableBeanFactory中實現了BeanDefinitionRegistry的接口,這個接口的實現完成BeanDefinition向容器的注冊。這個注冊過程不復雜,就是把解析得到的BeanDefinition設置到hashMap中去。需要注意的是,如果遇到同名的BeanDefinition的情況,進行處理的時候需要依據allowBeanDefinitionOverriding的配置來完成。具體的是實現如代碼清單2-21所示。
代碼清單2-21 BeanDefinitio注冊的實現
//--------------------------------------------------------------------- // Implementation of BeanDefinitionRegistry interface. //--------------------------------------------------------------------- public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "' beanName' must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException( beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } //注冊的過程需要synchronized,保證數據的一致性。 synchronized (this.beanDefinitionMap) { /** *這里檢查是不是有相同名字的BeanDefinition已經在IoC容器中注冊了,如果有 *相同名字的BeanDefinition,但又不允許覆蓋,那么拋出異常。 */ Object oldBeanDefinition = this.beanDefinitionMap.get(beanName); if (oldBeanDefinition ! = null) { if (! this.allowBeanDefinitionOverriding) { throw new BeanDefinitionStoreException( beanDefinition.getResource Description(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean ' " + beanName + "' : There is already [" + oldBeanDefinition + "] bound."); } else { if (this.logger.isInfoEnabled()) { this.logger.info("Overriding bean definition for bean ' " + beanName +"' : replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); } } } /** *這是正常注冊BeanDefinition的過程,把Bean的名字存入到beanDefinitionNames的同時, *把beanName作為Map的key,把beanDefinition作為value存入到IoC容器持有的 *beanDefinitionMap中去。 */ else { this.beanDefinitionNames.add(beanName); this.frozenBeanDefinitionNames = null; } this.beanDefinitionMap.put(beanName, beanDefinition); resetBeanDefinition(beanName); } }
完成了BeanDefinition的注冊,就完成了IoC容器的初始化過程。此時,在我們使用的IoC容器DefaultListableBeanFactory中已經建立了整個Bean的配置信息,而且這些BeanDefinition已經可以被容器使用了,它們都可在beanDefinitionMap里檢索和使用。容器的作用就是對這些信息進行處理和維護。這些信息是容器建立依賴反轉的基礎,有了這些基礎數據,下面我們接著看看在IoC容器中,依賴注入是怎樣完成的。
- Java 9 Programming Blueprints
- Mastering Unity Shaders and Effects
- Full-Stack Vue.js 2 and Laravel 5
- 精通網絡視頻核心開發技術
- Getting Started with LLVM Core Libraries
- Webpack實戰:入門、進階與調優
- Babylon.js Essentials
- Natural Language Processing with Python Quick Start Guide
- Xamarin Blueprints
- Learning TypeScript
- Dart:Scalable Application Development
- Scala編程(第4版)
- Internet of Things with Arduino Cookbook
- Learning Unity Physics
- jQuery權威指南