IOC Spring揭秘閱讀總結

2.1 我們的理念是:讓別人為你服務

????????IoC是隨著近年來輕量級容器(Lightweight Container)的興起而逐漸被很多人提起的一個名詞,它的全稱為Inversion of Control,中文通常翻譯為“控制反轉”,它還有一個別名叫做依賴注入(Dependency Injection)。

? ??????通常情況下,被注入對象會直接依賴于被依賴對象。但是,在IoC的場景中,二者之間通過IoC ServiceProvider來打交道,所有的被注入對象和依賴對象現在由IoC Service Provider統一管理。被注入對象需要什么,直接跟IoC Service Provider招呼一聲,后者就會把相應的被依賴對象注入到被注入對象中,從而達到IoC Service Provider為被注入對象服務的目的。IoC Service Provider在這里就是通常的IoC容器所充當的角色。從被注入對象的角度看,與之前直接尋求依賴對象相比,依賴對象的取得方式發生了反轉,控制也從被注入對象轉到了IoC Service Provider那里。

2.2 手語,呼喊,還是心有靈犀

? ??????IoC模式最權威的總結和解釋,應該是Martin Fowler的那篇文章“Inversion of Control Containers and the Dependency Injection pattern”,其中提到了三種依賴注入的方式,即構造方法注入(constructorinjection)、setter方法注入(setter injection)以及接口注入(interface injection)。下面讓我們詳細看一下這三種方式的特點及其相互之間的差別。

2.2.1 構造方法注入

????????顧名思義,構造方法注入,就是被注入對象可以通過在其構造方法中聲明依賴對象的參數列表, 讓外部(通常是IoC容器)知道它需要哪些依賴對象。

? ??????IoC Service Provider會檢查被注入對象的構造方法,取得它所需要的依賴對象列表,進而為其注入相應的對象。同一個對象是不可能被構造兩次的,因此,被注入對象的構造乃至其整個生命周期,應該是由IoC Service Provider來管理的。

????????構造方法注入方式比較直觀,對象被構造完成后,即進入就緒狀態,可以馬上使用。這就好比你剛進酒吧的門,服務生已經將你喜歡的啤酒擺上了桌面一樣。坐下就可馬上享受一份清涼與愜意。

2.2.2 setter方法注入

? ??????對于JavaBean對象來說,通常會通過setXXX()和getXXX()方法來訪問對應屬性。這些setXXX()方法統稱為setter方法,getXXX()當然就稱為getter方法。通過setter方法,可以更改相應的對象屬性,通過getter方法,可以獲得相應屬性的狀態。所以,當前對象只要為其依賴對象所對應的屬性添加setter方法,就可以通過setter方法將相應的依賴對象設置到被注入對象中。

? ??????setter方法注入雖不像構造方法注入那樣,讓對象構造完成后即可使用,但相對來說更寬松一些,可以在對象構造完成后再注入。這就好比你可以到酒吧坐下后再決定要點什么啤酒,可以要百威,也可以要大雪,隨意性比較強。如果你不急著喝,這種方式當然是最適合你的。

2.2.3 接口注入

????????相對于前兩種注入方式來說,接口注入沒有那么簡單明了。被注入對象如果想要IoC ServiceProvider為其注入依賴對象,就必須實現某個接口。這個接口提供一個方法,用來為其注入依賴對象。IoC Service Provider最終通過這些接口來了解應該為被注入對象注入什么依賴對象。

2.2.4 三種注入方式的比較

? ??????接口注入。從注入方式的使用上來說,接口注入是現在不甚提倡的一種方式,基本處于“退役狀態”。因為它強制被注入對象實現不必要的接口,帶有侵入性。而構造方法注入和setter方法注入則不需要如此。

????????構造方法注入。這種注入方式的優點就是,對象在構造完成之后,即已進入就緒狀態,可以 馬上使用。缺點就是,當依賴對象比較多的時候,構造方法的參數列表會比較長。而通過反射構造對象的時候,對相同類型的參數的處理會比較困難,維護和使用上也比較麻煩。而且在Java中,構造方法無法被繼承,無法設置默認值。對于非必須的依賴處理,可能需要引入多個構造方法,而參數數量的變動可能造成維護上的不便。

????????setter方法注入。因為方法可以命名,所以setter方法注入在描述性上要比構造方法注入好一些。 另外,setter方法可以被繼承,允許設置默認值,而且有良好的IDE支持。缺點當然就是對象無法在構造完成后馬上進入就緒狀態。

綜上所述,構造方法注入和setter方法注入因為其侵入性較弱,且易于理解和使用,所以是現在使用最多的注入方式;而接口注入因為侵入性較強,近年來已經不流行了

3.0 掌管大局的 Ioc Service Provider

3.1 IoC Service Provider 的職責

????????IoC Service Provider的職責相對來說比較簡單,主要有兩個:業務對象的構建管理和業務對象間的依賴綁定。

????????業務對象的構建管理。在IoC場景中,業務對象無需關心所依賴的對象如何構建如何取得,但這部分工作始終需要有人來做。所以,IoC Service Provider需要將對象的構建邏輯從客戶端對象那里剝離出來,以免這部分邏輯污染業務對象的實現。

????????業務對象間的依賴綁定。對于IoC Service Provider來說,這個職責是最艱巨也是最重要的,這是它的最終使命之所在。如果不能完成這個職責,那么,無論業務對象如何的“呼喊”,也不會得到依賴對象的任何響應(最常見的倒是會收到一個NullPointerException)。IoC Service Provider通過結合之前構建和管理的所有業務對象,以及各個業務對象間可以識別的依賴關系,將這些對象所依賴的對象注入綁定,從而保證每個業務對象在使用的時候,可以處于就緒狀態。

3.2 運籌帷幄的秘密——IoC Service Provider 如何管理對象間的依賴關系

? ??????前面我們說過,被注入對象可以通過多種方式通知IoC Service Provider為其注入相應依賴。但問 3題在于,收到通知的IoC Service Provider是否就一定能夠完全領會被注入對象的意圖,并及時有效地為其提供想要的依賴呢?有些時候,事情可能并非像我們所想象的那樣理所當然。當前流行的IoC Service Provider產品使用的注冊對象管理信息的方式主要有以下幾種。

3.2.1 直接編碼方式

? ??????當前大部分的IoC容器都應該支持直接編碼方式,比如PicoContainer、Spring、Avalon等。在容器啟動之前,我們就可以通過程序編碼的方式將被注入對象和依賴對象注冊到容器中,并明確它們相互之間的依賴注入關系。

3.2.2 配置文件方式

? ??????這是一種較為普遍的依賴注入關系管理方式。像普通文本文件、properties文件、XML文件等,都可以成為管理依賴注入關系的載體。不過,最為常見的,還是通過XML文件來管理對象注冊和對象間依賴關系,比如Spring IoC容器和在PicoContainer基礎上擴展的NanoContainer,都是采用XML文件來管理和保存依賴注入信息的。

3.2.3 元數據方式

????????這種方式的代表實現是Google Guice,這是Bob Lee在Java 5的注解和Generic的基礎上開發的一套IoC框架。我們可以直接在類中使用元數據信息來標注各個對象之間的依賴關系,然后由Guice框架根據這些注解所提供的信息將這些對象組裝后,交給客戶端對象使用

4.?Spring的IoC容器之BeanFactory

????????我們前面說過,Spring的IoC容器是一個IoC Service Provider,但是,這只是它被冠以IoC之名的部分原因,我們不能忽略的是“容器”。Spring的IoC容器是一個提供IoC支持的輕量級容器,除了基本的IoC支持,它作為輕量級容器還提供了IoC之外的支持。如在Spring的IoC容器之上,Spring還提供了相應的AOP框架支持、企業級服務集成等服務。Spring的IoC容器和IoC Service Provider所提供的服務之間存在一定的交集,二者的關系如圖4-1所示。


Spring提供了兩種容器類型:BeanFactory和ApplicationContext。

????????BeanFactory。基礎類型IoC容器,提供完整的IoC服務支持。如果沒有特殊指定,默認采用延遲初始化策略(lazy-load)。只有當客戶端對象需要訪問容器中的某個受管對象的時候,才對該受管對象進行初始化以及依賴注入操作。所以,相對來說,容器啟動初期速度較快,所需要的資源有限。對于資源有限,并且功能要求不是很嚴格的場景,BeanFactory是比較合適的IoC容器選擇。

????????ApplicationContext。ApplicationContext在BeanFactory的基礎上構建,是相對比較高級的容器實現,除了擁有BeanFactory的所有支持,ApplicationContext還提供了其他高級特性,比如事件發布、國際化信息支持等,這些會在后面詳述。ApplicationContext所管理的對象,在該類型容器啟動之后,默認全部初始化并綁定完成。所以,相對于BeanFactory來說,ApplicationContext要求更多的系統資源,同時,因為在啟動時就完成所有初始化,容器啟動時間較之BeanFactory也會長一些。在那些系統資源充足,并且要求更多功能的場景中,ApplicationContext類型的容器是比較合適的選擇。

? ??????BeanFactory,顧名思義,就是生產Bean的工廠。當然,嚴格來說,這個“生產過程”可能不像說起來那么簡單。既然Spring框架提倡使用POJO,那么把每個業務對象看作一個JavaBean對象,或許更容易理解為什么Spring的IoC基本容器會起這么一個名字。作為Spring提供的基本的IoC容器,BeanFactory可以完成作為IoC Service Provider的所有職責,包括業務對象的注冊和對象間依賴關系的綁定。

? ??????BeanFactory就像一個汽車生產廠。你從其他汽車零件廠商或者自己的零件生產部門取得汽車零件送入這個汽車生產廠,最后,只需要從生產線的終點取得成品汽車就可以了。相似地,將應用所需的所有業務對象交給BeanFactory之后,剩下要做的,就是直接從BeanFactory取得最終組裝完成并且可用的對象。至于這個最終業務對象如何組裝,你不需要關心,BeanFactory會幫你搞定。

????????所以,對于客戶端來說,與BeanFactory打交道其實很簡單。最基本地,BeanFactory肯定會公開一個取得組裝完成的對象的方法接口。

4.1 擁有BeanFactory之后的生活

? ??????確切地說,擁有BeanFactory之后的生活沒有太大的變化。當然,我的意思是看起來沒有太大的變化。到底引入BeanFactory后的生活是什么樣子,讓我們一起來體驗一下吧!

????????依然“拉拉扯扯的事情”。對于應用程序的開發來說,不管是否引入BeanFactory之類的輕量級容器,應用的設計和開發流程實際上沒有太大改變。換句話說,針對系統和業務邏輯,該如何設計和實現當前系統不受是否引入輕量級容器的影響。

4.2 BeanFactory的對象注冊與依賴綁定方式

? ??????BeanFactory作為一個IoC Service Provider,為了能夠明確管理各個業務對象以及業務對象之間的依賴綁定關系,同樣需要某種途徑來記錄和管理這些信息。上一章在介紹IoC Service Provider時,我們提到通常會有三種方式來管理這些信息。而BeanFactory幾乎支持所有這些方式,很令人興奮,不是嗎?

4.2.1 直接編碼方式

? ??????其實,把編碼方式單獨提出來稱作一種方式并不十分恰當。因為不管什么方式,最終都需要編碼才能“落實”所有信息并付諸使用。

? ??????BeanFactory只是一個接口,我們最終需要一個該接口的實現來進行實際的Bean的管理,Default- ListableBeanFactory就是這么一個比較通用的BeanFactory實現類。DefaultListableBean-Factory除了間接地實現了BeanFactory接口,還實現了BeanDefinitionRegistry接口,該接口才是在BeanFactory的實現中擔當Bean注冊管理的角色。基本上,BeanFactory接口只定義如何訪問容器內管理的Bean的方法,各個BeanFactory的具體實現類負責具體Bean的注冊以及管理工作。BeanDefinitionRegistry接口定義抽象了Bean的注冊邏輯。通常情況下,具體的BeanFactory實現類會實現這個接口來管理Bean的注冊。


?4.2.2 外部配置文件方式

????????Spring的IoC容器支持兩種配置文件格式:Properties文件格式和XML文件格式。當然,如果你愿意也可以引入自己的文件格式,前提是真的需要。

????????采用外部配置文件時,Spring的IoC容器有一個統一的處理方式。通常情況下,需要根據不同的外部配置文件格式,給出相應的BeanDefinitionReader實現類,由BeanDefinitionReader的相應實現類負責將相應的配置文件內容讀取并映射到BeanDefinition,然后將映射后的BeanDefinition注冊到一個BeanDefinitionRegistry,之后,BeanDefinitionRegistry即完成Bean的注冊和加載。當然,大部分工作,包括解析文件格式、裝配BeanDefinition之類的工作,都是由BeanDefinition-Reader的相應實現類來做的,BeanDefinitionRegistry只不過負責保管而已。

1. Properties配置格式的加載

????????Spring提供了org.springframework.beans.factory.support.PropertiesBeanDefinition-Reader類用于Properties格式配置文件的加載,所以,我們不用自己去實現BeanDefinitionReader,只要根據該類的讀取規則,提供相應的配置文件即可。

2. XML配置格式的加載

? ??????XML配置格式是Spring支持最完整,功能最強大的表達方式。當然,一方面這得益于XML良好的語意表達能力;另一方面,就是Spring框架從開始就自始至終保持XML配置加載的統一性。同Properties配置加載類似,現在只不過是轉而使用XML而已。Spring 2.x之前,XML配置文件采用DTD(DocumentType Definition)實現文檔的格式約束。2.x之后,引入了基于XSD(XML Schema Definition)的約束方式。不過,原來的基于DTD的方式依然有效,因為從DTD轉向XSD只是“形式”上的轉變,所以,后面的大部分講解還會沿用DTD的方式,只有必要時才會給出特殊說明。

4.2.3 注解方式

????????可能你沒有注意到,我在提到BeanFactory所支持的對象注冊與依賴綁定方式的時候,說的是BeanFactory“幾乎”支持IoC Service Provider可能使用的所有方式。之所以這么說,有兩個原因。

? 在Spring 2.5發布之前,Spring框架并沒有正式支持基于注解方式的依賴注入;

? Spring 2.5發布的基于注解的依賴注入方式,如果不使用classpath-scanning功能的話,仍然部分依賴于“基于XML配置文件”的依賴注入方式。

????????另外,注解是Java 5之后才引入的,所以,以下內容只適用于應用程序使用了Spring 2.5以及Java 5或者更高版本的情況之下。

????????如果要通過注解標注的方式為FXNewsProvider注入所需要的依賴,現在可以使用@Autowired以及@Component對相關類進行標記。

4.3 BeanFactory的XML之旅

????????XML格式的容器信息管理方式是Spring提供的最為強大、支持最為全面的方式。從Spring的參考文檔到各Spring相關書籍,都是按照XML的配置進行說明的,這部分內容可以讓你充分領略到Spring的IoC容器的魅力,以致于我們也不得不帶你初次或者再次踏上Spring的XML之旅。

4.3.1 <beans>和<bean>

? ??????所 有 使 用 XML 文件進行配置信息加載的Spring IoC 容器, 包括BeanFactory 和ApplicationContext的所有XML相應實現,都使用統一的XML格式。在Spring 2.0版本之前,這種格式由Spring提供的DTD規定,也就是說,所有的Spring容器加載的XML配置文件的頭部,都需要以下形式的DOCTYPE聲明:

????????從Spring 2.0版本之后,Spring在繼續保持向前兼容的前提下,既可以繼續使用DTD方式的DOCTYPE進行配置文件格式的限定,又引入了基于XML Schema的文檔聲明。所以,Spring 2.0之后,同樣可以使用代碼清單4-11所展示的基于XSD的文檔聲明。


????????不過,不管使用哪一種形式的文檔聲明,實際上限定的元素基本上是相同的。讓我們從最頂層的元素開始,看一下這兩種文檔聲明都限定了哪些元素吧!所有注冊到容器的業務對象,在Spring中稱之為Bean。所以,每一個對象在XML中的映射也自然而然地對應一個叫做的元素。既然容器最終可以管理所有的業務對象,那么在XML中把這些叫做的元素組織起來的,就叫做。多個組成一個很容易理解,不是嗎?

1.<beans>之唯我獨尊

? ? ? ? <beans>是XML配置文件中最頂層的元素,它下面可以包含0或者1個和多個<bean>以及<import>或者<alias>,如圖4-4所示。

? ? ? ?<beans> 作為所有的“統帥”,它擁有相應的屬性(attribute)對所轄的<bean>進行統一的默認行為設置,包括如下幾個。

? default-lazy-init。其值可以指定為true或者false,默認值為false。用來標志是否對所有的進行延遲初始化。

? default-autowire。可以取值為no、byName、byType、constructor以及autodetect。默認值為no,如果使用自動綁定的話,用來標志全體bean使用哪一種默認綁定方式。

? default-dependency-check。可以取值none、objects、simple以及all,默認值為none,即不做依賴檢查。? default-init-method。如果所管轄的按照某種規則,都有同樣名稱的初始化方法的話,可以在這里統一指定這個初始化方法名,而不用在每一個上都重復單獨指定。

? default-destroy-method。與default-init-method相對應,如果所管轄的bean有按照某種規則使用了相同名稱的對象銷毀方法,可以通過這個屬性統一指定。

2.<description>、<import>和<alias>

?????????之所以把這幾個元素放到一起講解,是因為通常情況下它們不是必需的。不過,了解一下也沒什么不好,不是嗎?

<description>

????????可以通過在配置的文件中指定一些描述性的信息。通常情況下,該元素是省略的。當然,如果愿意,隨時可以為我們效勞。

<import>

? ??????通常情況下,可以根據模塊功能或者層次關系,將配置信息分門別類地放到多個配置文件中。在 5想加載主要配置文件,并將主要配置文件所依賴的配置文件同時加載時,可以在這個主要的配置文件中通過元素對其所依賴的配置文件進行引用。比如,如果A.xml中的<bean>定義可能依賴B.xml中的某些<bean>定義,那么就可以在A.xml中使用<import>將B.xml引入到A.xml,以類似于<import resource="B.xml" />的形式。

????????但是,這個功能在我看來價值不大,因為容器實際上可以同時加載多個配置,沒有必要非通過一個配置文件來加載所有配置。不過,或許在有些場景中使用這種方式比較方便也說不定。

<alias>

? ??????可以通過<alias>為某些<bean>起一些“外號”(別名),通常情況下是為了減少輸入。比如,假設有個<bean>,它的名稱為dataSourceForMasterDatabase,你可以為其添加一個<alias>,像這樣<alias name = "dataSourceForMasterDatabase" alias = "masterDataSource"/>。以后通過dataSourceForMasterDatabase或者masterDataSource來引用這個<bean>都可以,只要你覺得方便就行。

4.3.2 孤孤單單一個人

? ?????哦,錯了,是孤孤單單一個Bean。每個業務對象作為個體,在Spring的XML配置文件中是與11元素一一對應的。窺一斑而知全豹,只要我們了解單個的業務對象是如何配置的,剩下的就可以“依葫蘆畫瓢”了。所以,讓我們先從最簡單的單一對象配置開始吧!如下代碼演示了最基礎的對象配置形式:

? ? ? ? <bean id = "djNewsListener" class = "..impl.DowJonesNewListener"></bean>

id屬性

????????通常,每個注冊到容器的對象都需要一個唯一標志來將其與“同處一室”的“兄弟們”區分開來,就好像我們每一個人都有一個身份證號一樣(重號的話就比較麻煩)。通過id屬性來指定當前注冊對象的beanName是什么。這里,通過id指定beanName為djNewsListener。實際上,并非任何情況下都需要指定每個<bean>的id,有些情況下,id可以省略,比如后面會提到的內部<bean>以及不需要根據beanName明確依賴關系的場合等。

????????除了可以使用id來指定在容器中的標志,還可以使用name屬性來指定<bean>的別(alias)。比如,以上定義,我們還可以像如下代碼這樣,為其添加別名:

? ? ? ? <bean id ="djNewsListener" name = "/news/djNewsListener,dowJonesNewsListener" class= "...impl.DownJonesNewsListener"> </bean>與id屬性相比,name屬性的靈活之處在于,name可以使用id不能使用的一些字符,比如/。而且還可以通過逗號、空格或者冒號分割指定多個name。name的作用跟使用為id指定多個別名基本相同.

class屬性

每個注冊到容器的對象都需要通過元素的class屬性指定其類型,否則,容器可不知道這

個對象到底是何方神圣。在大部分情況下,該屬性是必須的。僅在少數情況下不需要指定,如后面將提到的在使用抽象配置模板的情況下。

4.3.3 Help Me, Help You

????????在大部分情況下,你不太可能選擇單獨“作戰”,業務對象也是;各個業務對象之間會相互協作來更好地完成同一使命。這時,各個業務對象之間的相互依賴就是無法避免的。對象之間需要相互協作,在橫向上它們存在一定的依賴性。而現在我們就是要看一下,在Spring的IoC容器的XML配置中,應該如何表達這種依賴性。

????????既然業務對象現在都符合IoC的規則,那么要了解的表達方式其實也很簡單,無非就是看一下構造方法注入和setter方法注入通過XML是如何表達的而已。那么,讓我們開始吧!

1. 構造方法注入的XML之道

????????按照Spring的IoC容器配置格式,要通過構造方法注入方式,為當前業務對象注入其所依賴的對象,需要使用。正常情況下,如以下代碼所示:??

? ??????對于元素,稍后會進行詳細說明。這里你只需要知道,通過這個元素來指明容器將為djNewsProvider這個注入通過所引用的Bean實例。這種方式可能看起來或者編寫起來不是很簡潔,最新版本的Spring也支持配置簡寫形式,如以下代碼所示:

????????簡潔多了不是嘛?其實,無非就是表達方式不同而已,實際達到的效果是一樣的。有些時候,容器在加載XML配置的時候,因為某些原因,無法明確配置項與對象的構造方法參數列表的一一對應關系,就需要請的type或者index屬性出馬。比如,對象存在多個構造方法,當參數列表數目相同而類型不同的時候,容器無法區分應該使用哪個構造方法來實例化對象,或者構造方法可能同時傳入最少兩個類型相同的對象。

type屬性

index屬性

????????當某個業務對象的構造方法同時傳入了多個類型相同的參數時,Spring又該如何將這些配置中的信息與實際對象的參數一一對應呢?好在,如果配置項信息和對象參數可以按照順序初步對應的話, Spring還是可以正常工作的

2. setter方法注入的XML之道

????????與構造方法注入可以使用注入配置相對應,Spring為setter方法注入提供了<property>元素。

3.<property>和<constructor-arg>中可用的配置項

? ??????之前我們看到,可以通過在和這兩個元素內部嵌套或者,來指定將為當前對象注入的簡單數據類型或者某個對象的引用。不過,為了能夠指定多種注入類型,Spring還提供了其他的元素供我們使用,這包括bean、ref、idref、value、null、list、set、map、props。下面我們來逐個詳細講述它們。

(1)可以通過value為主體對象注入簡單的數據類型,不但可以指定String類型的數據,而且可以指定其他Java語言中的原始類型以及它們的包裝器(wrapper)類型,比如int、Integer等。容器在注入的時候,會做適當的轉換工作(我們會在后面揭示轉換的奧秘)。

(2)使用ref來引用容器中其他的對象實例,可以通過ref的local、parent和bean屬性來指定引用的對象的beanName是什么。

local、parent和bean的區別在于:

? local只能指定與當前配置的對象在同一個配置文件的對象定義的名稱(可以獲得XML解析器的id約束驗證支持);

? parent則只能指定位于當前容器的父容器中定義的對象引用;

? bean則基本上通吃,所以,通常情況下,直接使用bean來指定對象引用就可以了。

(3)<idref>。如果要為當前對象注入所依賴的對象的名稱,而不是引用,那么通常情況下,可以 使用來達到這個目的

(4) 內部<bean>。使用<ref>可以引用容器中獨立定義的對象定義。但有時,可能我們所依賴的對象只有當前一個對象引用,或者某個對象定義我們不想其他對象通過<ref>引用到它。這時,我們可以使用內嵌的<bean>,將這個私有的對象定義僅局限在當前對象。

5)<list>對應注入對象類型為java.util.List及其子類或者數組類型的依賴對象。

4. depends-on

????????通常情況下,可以直接通過之前提到的所有元素,來顯式地指定bean之間的依賴關系。這樣,容器在初始化當前bean定義的時候,會根據這些元素所標記的依賴關系,首先實例化當前bean定義所依賴的其他bean定義。但是,如果某些時候,我們沒有通過類似的元素明確指定對象A依賴于對象B的話,如何讓容器在實例化對象A之前首先實例化對象B呢?

考慮以下所示代碼:

????????系統中所有需要日志記錄的類,都需要在這些類使用之前首先初始化log4j。那么,就會非顯式地依賴于SystemConfigurationSetup的靜態初始化塊。如果ClassA需要使用log4j,那么就必須在bean定義中使用depends-on來要求容器在初始化自身實例之前首先實例化SystemConfigurationSetup,以保證日志系統的可用,如下代碼演示的正是這種情況:

5. autowire

????????除了可以通過配置明確指定bean之間的依賴關系,Spirng還提供了根據bean定義的某些特點將相互依賴的某些bean直接自動綁定的功能。通過的autowire屬性,可以指定當前bean定義采用某種類型的自動綁定模式。這樣,你就無需手工明確指定該bean定義相關的依賴關系,從而也可以免去一些手工輸入的工作量。

????????Spring提供了5種自動綁定模式,即no、byName、byType、constructor和autodetect,下面是它們的具體介紹。

? no

????????容器默認的自動綁定模式,也就是不采用任何形式的自動綁定,完全依賴手工明確配置各個bean之間的依賴關系

? byName

????????按照類中聲明的實例變量的名稱,與XML配置文件中聲明的bean定義的beanName的值進行匹配,相匹配的bean定義將被自動綁定到當前實例變量上。這種方式對類定義和配置的bean定義有一定的限制。假設我們有如下所示的類定義:


????????需要注意兩點:第一,我們并沒有明確指定fooBean的依賴關系,而僅指定了它的autowire屬性為byName;第二,第二個bean定義的id為emphasisAttribute,與Foo類中的實例變量名稱相同。

? byType

????????如果指定當前bean定義的autowire模式為byType,那么,容器會根據當前bean定義類型,分析其相應的依賴對象類型,然后到容器所管理的所有bean定義中尋找與依賴對象類型相同的bean定義,然后將找到的符合條件的bean自動綁定到當前bean定義。

????????對于byName模式中的實例類Foo來說,容器會在其所管理的所有bean定義中尋找類型為Bar的bean定義。如果找到,則將找到的bean綁定到Foo的bean定義;如果沒有找到,則不做設置。但如果找到多個,容器會告訴你它解決不了“該選用哪一個”的問題,你只好自己查找原因,并自己修正該問題。所以,byType只能保證,在容器中只存在一個符合條件的依賴對象的時候才會發揮最大的作用,如果容器中存在多個相同類型的bean定義,那么,不好意思,采用手動明確配置吧!

????????指定byType類型的autowire模式與byName沒什么差別,只是autowire的值換成byType而已。

? constructor

? ??????byName和byType類型的自動綁定模式是針對property的自動綁定,而constructor類型則是針對構造方法參數的類型而進行的自動綁定,它同樣是byType類型的綁定模式。不過,constructor是匹配構造方法的參數類型,而不是實例屬性的類型。與byType模式類似,如果找到不止一個符合條件的bean定義,那么,容器會返回錯誤。使用上也與byType沒有太大差別,只不過是應用到需要使用構造方法注入的bean定義之上,代碼清單4-23給出了一個使用construtor模式進行自動綁定的簡單場景演示。

? ??????
? autodetect

? ??????這種模式是byType和constructor模式的結合體,如果對象擁有默認無參數的構造方法,容器會優先考慮byType的自動綁定模式。否則,會使用constructor模式。當然,如果通過構造方法注入綁定后還有其他屬性沒有綁定,容器也會使用byType對剩余的對象屬性進行自動綁定。

自動綁定和手動明確綁定各有利弊。自動綁定的優點有如下兩點。

(1) 某種程度上可以有效減少手動敲入配置信息的工作量。

(2) 某些情況下,即使為當前對象增加了新的依賴關系,但只要容器中存在相應的依賴對象,就不需要更改任何配置信息。

自動綁定的缺點有如下幾點。

(1) 自動綁定不如明確依賴關系一目了然。我們可以根據明確的依賴關系對整個系統有一個明確的認識,但使用自動綁定的話,就可能需要在類定義以及配置文件之間,甚至各個配置文件之間來回轉換以取得相應的信息。

(2) 某些情況下,自動綁定無法滿足系統需要,甚至導致系統行為異常或者不可預知。根據類型(byType)匹配進行的自動綁定,如果系統中增加了另一個相同類型的bean定義,那么整個系統就會崩潰;根據名字(byName)匹配進行的自動綁定,如果把原來系統中相同名稱的bean定義類型給換掉,就會造成問題,而這些可能都是在不經意間發生的。

(3) 使用自動綁定,我們可能無法獲得某些工具的良好支持,比如Spring IDE。

6. dependency-check

? ??????我們可以使用每個的dependency-check屬性對其所依賴的對象進行最終檢查該功能主要與自動綁定結合使用,可以保證當自動綁定完成后,最終確認每個對象所依賴的對象是否按照所預期的那樣被注入。當然,并不是說不可以與平常的明確綁定方式一起使用。

7. lazy-init

? ??????延遲初始化(lazy-init)這個特性的作用,主要是可以針對ApplicationContext容器的bean初始化行為施以更多控制。與BeanFactory不同,ApplicationContext在容器啟動的時候,就會馬上對所有的“singleton的bean定義”①進行實例化操作。通常這種默認行為是好的,因為如果系統有問題的話,可以在第一時間發現這些問題,但有時,我們不想某些bean定義在容器啟動后就直接實例化,可能出于容器啟動時間的考慮,也可能出于其他原因的考慮。總之,我們想改變某個或者某些bean定義在ApplicationContext容器中的默認實例化時機。這時,就可以通過的lazy-init屬性來控制這種初始化行為.

4.3.4 繼承?我也會!

????????使用parent屬性,只需要將特定的屬性進行更改,而不要全部又重新定義一遍。

4.3.5 bean的 scope

? ??????多數中文資料在講解bean的scope時喜歡用“作用域”這個名詞,scope用來聲明容器中的對象所應該處的限定場景或者說該對象的存活時間,即容器在對象進入其相應的scope之前,生成并裝配這些對象,在該對象不再處于這些scope的限定之后,容器通常會銷毀這些對象。

? ??????Spring容器最初提供了兩種bean的scope類型:singleton和prototype,但發布2.0之后,又引入了另外三種scope類型,即request、session和global session類型。不過這三種類型有所限制,只能在Web應用中使用。也就是說,只有在支持Web應用的ApplicationContext中使用這三個scope才是合理的。

1. singleton

????????配置中的bean定義可以看作是一個模板,容器會根據這個模板來構造對象。但是要根據這個模板構造多少對象實例,又該讓這些構造完的對象實例存活多久,則由容器根據bean定義的scope語意來決定。標記為擁有singleton scope的對象定義,在Spring的IoC容器中只存在一個實例,所有對該對象的引用將共享這個實例。該實例從容器啟動,并因為第一次被請求而初始化之后,將一直存活到容器退出,也就是說,它與IoC容器“幾乎”擁有相同的“壽命”。

可以從兩個方面來看待singleton的bean所具有的特性。

? 對象實例數量。singleton類型的bean定義,在一個容器中只存在一個共享實例,所有對該類型bean的依賴都引用這一單一實例。這就好像每個幼兒園都會有一個滑梯一樣,這個幼兒園的小朋友共同使用這一個滑梯。而對于該幼兒園容器來說,滑梯實際上就是一個singleton的bean。

? 對象存活時間。singleton類型bean定義,從容器啟動,到它第一次被請求而實例化開始,只要容器不銷毀或者退出,該類型bean的單一實例就會一直存活。

????????通常情況下,如果你不指定bean的scope,singleton便是容器默認的scope,所以,下面三種配置形式實際上達成的是同樣的效果:

2. prototype

? ??????針對聲明為擁有prototype scope的bean定義,容器在接到該類型對象的請求的時候,會每次都重新生成一個新的對象實例給請求方。雖然這種類型的對象的實例化以及屬性設置等工作都是由容器負責的,但是只要準備完畢,并且對象實例返回給請求方之后,容器就不再擁有當前返回對象的引用,請求方需要自己負責當前返回對象的后繼生命周期的管理工作,包括該對象的銷毀。也就是說,容器每次返回給請求方一個新的對象實例之后,就任由這個對象實例“自生自滅”了。

? ?????對于那些請求方不能共享使用的對象類型,應該將其bean定義的scope設置為prototype。這樣,每個請求方可以得到自己對應的一個對象實例,而不會出現上面“哭鼻子”的現象。通常,聲明為prototype的scope的bean定義類型,都是一些有狀態的,比如保存每個顧客信息的對象

3. request、session和global session

? ??????這三個scope類型是Spirng 2.0之后新增加的,它們不像之前的singleton和prototype那么“通用”,因為它們只適用于Web應用程序,通常是與XmlWebApplicationContext共同使用,而這些將在第6部分詳細討論。不過,既然它們也屬于scope的概念,這里就簡單提幾句。

? request

????????Spring容器,即XmlWebApplicationContext會為每個HTTP請求創建一個全新的Request-Processor對象供當前請求使用,當請求結束后,該對象實例的生命周期即告結束。當同時有10個HTTP請求進來的時候,容器會分別針對這10個請求返回10個全新的RequestProcessor對象實例,且它們之間互不干擾。從不是很嚴格的意義上說,request可以看作prototype的一種特例,除了場景更加具體之外,語意上差不多。

? session

? ??????對于Web應用來說,放到session中的最普遍的信息就是用戶的登錄信息,對于這種放到session中的信息,我們可使用如下形式指定其scope為session:

? ??????Spring容器會為每個獨立的session創建屬于它們自己的全新的UserPreferences對象實例。與request相比,除了擁有session scope的bean的實例具有比request scope的bean可能更長的存活時間,其他方面真是沒什么差別。

? global session

????????還是userPreferences,不過scope對應的值換一下。global session只有應用在基于portlet的Web應用程序中才有意義,它映射到portlet的global范圍的 session。如果在普通的基于servlet的Web應用中使用了這個類型的scope,容器會將其作為普通的session類型的scope對待。

4. 自定義scope類型

????????在Spring 2.0之后的版本中,容器提供了對scope的擴展點,這樣,你可以根據自己的需要或者應用的場景,來添加自定義的scope類型。需要說明的是,默認的singleton和prototype是硬編碼到代碼中的,而request、session和global session,包括自定義scope類型,則屬于可擴展的scope行列,它們都實現了rg.springframework.beans.factory.config.Scope接口。

4.3.6 工廠方法與FactoryBean

????????在強調“面向接口編程”的同時,有一點需要注意:雖然對象可以通過聲明接口來避免對特定接口實現類的過度耦合,但總歸需要一種方式將聲明依賴接口的對象與接口實現類關聯起來。否則,只依賴一個不做任何事情的接口是沒有任何用處的。

? ??????通常的做法是通過使用工廠方法(Factory Method)模式,提供一個工廠類來實例化具體的接口實現類,這樣,主體對象只需要依賴工廠類,具體使用的實現類有變更的話,只是變更工廠類,而主體對象不需要做任何變動。

????????針對使用工廠方法模式實例化對象的方式,Spring的IoC容器同樣提供了對應的集成支持。我們所要做的,只是將工廠類所返回的具體的接口實現類注入給主體對象(這里是Foo)。

1. 靜態工廠方法(Static Factory Method)? ? ? ??

2. 非靜態工廠方法(Instance Factory Method)

3. FactoryBean

????????FactoryBean是Spring容器提供的一種可以擴展容器對象實例化邏輯的接口,請不要將其與容器名稱BeanFactory相混淆。FactoryBean,其主語是Bean,定語為Factory,也就是說,它本身與其他注冊到容器的對象一樣,只是一個Bean而已,只不過,這種類型的Bean本身就是生產對象的工廠(Factory)。

????????當某些對象的實例化過程過于煩瑣,通過XML配置過于復雜,使我們寧愿使用Java代碼來完成這個實例化過程的時候,或者,某些第三方庫不能直接注冊到Spring容器的時候,就可以實現org.springframework.beans.factory.FactoryBean接口,給出自己的對象實例化邏輯代碼。當然,不使用FactoryBean,而像通常那樣實現自定義的工廠方法類也是可以的。不過,FactoryBean可是Spring提供的對付這種情況的“制式裝備”哦!

4.3.7 偷梁換柱之術

????????在學習以下內容之前,先提一下有關bean的scope的使用“陷阱”,特別是prototype在容器中的使用,以此引出本節將要介紹的Spring容器較為獨特的功能特性:方法注入(Method Injection)以及方法替換(Method Replacement)。

????????我們知道,擁有prototype類型scope的bean,在請求方每次向容器請求該類型對象的時候,容器都會返回一個全新的該對象實例。為了簡化問題的敘述,我們直接將FX News系統中的FXNewsBean定義注冊到容器中,并將其scope設置為prototype。因為它是有狀態的類型,每條新聞都應該是新的獨立個體;同時,我們給出MockNewsPersister類,使其實現IFXNewsPersister接口,以模擬注入FXNewsBean實例后的情況。這樣,我們就有了代碼清單4-35所展示的類聲明和相關配置。

??從輸出看,對象實例是相同的,而這與我們的初衷是相悖的。因為每次調用persistNews都會調用getNewsBean()方法并返回一個FXNewsBean實例,而FXNewsBean實例是prototype類型的,因此每次不是應該輸出不同的對象實例嘛?

????????好了,問題實際上不是出在FXNewsBean的scope類型是否是prototype的,而是出在實例的取得方式上面。雖然FXNewsBean擁有prototype類型的scope,但當容器將一個FXNewsBean的實例注入MockNewsPersister之后,MockNewsPersister就會一直持有這個FXNewsBean實例的引用。雖然每次輸出都調用了getNewsBean()方法并返回了FXNewsBean 的實例,但實際上每次返回的都是MockNewsPersister持有的容器第一次注入的實例。這就是問題之所在。換句話說,第一個實例注入后,MockNewsPersister再也沒有重新向容器申請新的實例。所以,容器也不會重新為其注入新的FXNewsBean類型的實例。知道原因之后,我們就可以解決這個問題了。解決問題的關鍵在于保證getNewsBean()方法每次從容器中取得新的FXNewsBean實例,而不是每次都返回其持有的單一實例。

1. 方法注入

????????Spring容器提出了一種叫做方法注入(Method Injection)的方式,可以幫助我們解決上述問題。我們所要做的很簡單,只要讓getNewsBean方法聲明符合規定的格式,并在配置文件中通知容器,當該方法被調用的時候,每次返回指定類型的對象實例即可。方法聲明需要符合的規格定義如下:

????????也就是說,該方法必須能夠被子類實現或者覆寫,因為容器會為我們要進行方法注入的對象使用Cglib動態生成一個子類實現,從而替代當前對象。既然我們的getNewsBean()方法已經滿足以上方法聲明格式,剩下唯一要做的就是配置該類,配置內容如下所示:


? ??????通過的name屬性指定需要注入的方法名,bean屬性指定需要注入的對象,當getNewsBean方法被調用的時候,容器可以每次返回一個新的FXNewsBean類型的實例。

2. 殊途同歸? ??????

????????除了使用方法注入來達到“每次調用都讓容器返回新的對象實例”的目的,還可以使用其他方式達到相同的目的。下面給出其他兩種解決類似問題的方法,供讀者參考。

? 使用BeanFactoryAware接口我們知道,即使沒有方法注入,只要在實現getNewsBean()方法的時候,能夠保證每次調用Bean- Factory的getBean("newsBean"),就同樣可以每次都取得新的FXNewsBean對象實例。現在,我們唯一需要的,就是讓MockNewsPersister擁有一個BeanFactory的引用。

????????Spring框架提供了一個BeanFactoryAware接口,容器在實例化實現了該接口的bean定義的過程中,會自動將容器本身注入該bean。這樣,該bean就持有了它所處的BeanFactory的引用。

? 使用ObjectFactoryCreatingFactoryBean

? ??????ObjectFactoryCreatingFactoryBean是Spring 提供的一個FactoryBean實現,它返回一個ObjectFactory實例。從ObjectFactoryCreatingFactoryBean返回的這個ObjectFactory實例可以為我們返回容器管理的相關對象。實際上, ObjectFactoryCreatingFactoryBean 實現了BeanFactoryAware接口,它返回的ObjectFactory實例只是特定于與Spring容器進行交互的一個實現而已。使用它的好處就是,隔離了客戶端對象對BeanFactory的直接引用。

3. 方法替換

? ??????與方法注入只是通過相應方法為主體對象注入依賴對象不同,方法替換更多體現在方法的實現層面上,它可以靈活替換或者說以新的方法實現覆蓋掉原來某個方法的實現邏輯。基本上可以認為,方法替換可以幫助我們實現簡單的方法攔截功能。要知道,我們現在可是在不知不覺中邁上了AOP的大道哦!

4.4 容器背后的秘密????????

????????子曰:學而不思則罔。除了了解Spring的IoC容器如何使用,了解Spring的IoC容器都提供了哪些功能,我們也應該想一下,Spring的IoC容器內部到底是如何來實現這些的呢?雖然我們不太可能“重新發明輪子”,但是,如圖4-7(該圖摘自Spring官方參考文檔)所示的那樣,只告訴你“Magic Happens Here”,你是否就能心滿意足呢?

4.4.1 “戰略性觀望”

????????Spring的IoC容器所起的作用,就像圖4-7所展示的那樣,它會以某種方式加載ConfigurationMetadata(通常也就是XML格式的配置信息),然后根據這些信息綁定整個系統的對象,最終組裝成一個可用的基于輕量級容器的應用系統。

????????Spring的IoC容器實現以上功能的過程,基本上可以按照類似的流程劃分為兩個階段,即容器啟動 階段和Bean實例化階段,如圖4-8所示。

????????Spring的IoC容器在實現的時候,充分運用了這兩個實現階段的不同特點,在每個階段都加入了相應的容器擴展點,以便我們可以根據具體場景的需要加入自定義的擴展邏輯。


1. 容器啟動階段

????????容器啟動伊始,首先會通過某種途徑加載Configuration MetaData。除了代碼方式比較直接,在大部分情況下,容器需要依賴某些工具類(BeanDefinitionReader)對加載的Configuration MetaData進行解析和分析,并將分析后的信息編組為相應的BeanDefinition,最后把這些保存了bean定義必要信息的BeanDefinition,注冊到相應的BeanDefinitionRegistry,這樣容器啟動工作就完成了。圖4-9演示了這個階段的主要工作。

????????總地來說,該階段所做的工作可以認為是準備性的,重點更加側重于對象管理信息的收集。當然,一些驗證性或者輔助性的工作也可以在這個階段完成。

2. Bean實例化階段

? ??????經過第一階段,現在所有的bean定義信息都通過BeanDefinition的方式注冊到了BeanDefinitionRegistry中。當某個請求方通過容器的getBean方法明確地請求某個對象,或者因依賴關系容器需要隱式地調用getBean方法時,就會觸發第二階段的活動。

????????該階段,容器會首先檢查所請求的對象之前是否已經初始化。如果沒有,則會根據注冊的BeanDefinition所提供的信息實例化被請求對象,并為其注入依賴。如果該對象實現了某些回調接口,也會根據回調接口的要求來裝配它。當該對象裝配完畢之后,容器會立即將其返回請求方使用。如果說第一階段只是根據圖紙裝配生產線的話,那么第二階段就是使用裝配好的生產線來生產具體的產品了。

4.4.2 插手“容器的啟動”

? ??????Spring提供了一種叫做BeanFactoryPostProcessor的容器擴展機制。該機制允許我們在容器實例化相應對象之前,對注冊到容器的BeanDefinition所保存的信息做相應的修改。這就相當于在容器實現的第一階段最后加入一道工序,讓我們對最終的BeanDefinition做一些額外的操作,比如修改其中bean定義的某些屬性,為bean定義增加其他信息等。

????????如果要自定義實現BeanFactoryPostProcessor,通常我們需要實現org.springframework.beans.factory.config.BeanFactoryPostProcessor接口。同時,因為一個容器可能擁有多個Bean-FactoryPostProcessor,這個時候可能需要實現類同時實現Spring的org.springframework.core.Ordered接口,以保證各個BeanFactoryPostProcessor可以按照預先設定的順序執行(如果順序緊要的話)。但是,因為Spring已經提供了幾個現成的BeanFactoryPostProcessor實現類,所以,大多時候,我們很少自己去實現某個BeanFactoryPostProcessor。其中,org.springframework.beans.factory.config.PropertyPlaceholderConfigurer和org.springframework.beans.factory.config.Property OverrideConfigurer是兩個比較常用的BeanFactoryPostProcessor。另外,為了處理配置文件中的數據類型與真正的業務對象所定義的數據類型轉換,Spring還允許我們通過org.springframework.beans.factory.config.CustomEditorConfigurer來注冊自定義的PropertyEditor以補助容器中默認的PropertyEditor。可以參考BeanFactoryPostProcessor的Javadoc來了解更多其實現子類的情況。

????????我 們 可 以 通 過兩種方式來應用BeanFactoryPostProcessor , 分別針對基本的IoC 容器BeanFactory和較為先進的容器ApplicationContext。

????????對于BeanFactory來說,我們需要用手動方式應用所有的BeanFactoryPostProcessor

????????對于ApplicationContext來說,情況看起來要好得多。因為ApplicationContext會自動識別配置文件中的BeanFactoryPostProcessor并應用它,所以,相對于BeanFactory,在ApplicationContext中加載并應用BeanFactoryPostProcessor,僅需要在XML配置文件中將這些BeanFactoryPost-Processor簡單配置一下即可。

下面讓我們看一下Spring提供的這幾個BeanFactoryPostProcessor實現都可以完成什么功能。

1. PropertyPlaceholderConfigurer?

????????通常情況下,我們不想將類似于系統管理相關的信息同業務對象相關的配置信息混雜到XML配置文件中,以免部署或者維護期間因為改動繁雜的XML配置文件而出現問題。我們會將一些數據庫連接信息、郵件服務器等相關信息單獨配置到一個properties文件中,這樣,如果因系統資源變動的話,只需要關注這些簡單properties配置文件即可。

????????PropertyPlaceholderConfigurer允許我們在XML配置文件中使用占位符(PlaceHolder),并將這些占位符所代表的資源單獨配置到簡單的properties文件中來加載。

2. PropertyOverrideConfigurer

????????PropertyPlaceholderConfigurer可以通過占位符,來明確表明bean定義中的property與 2properties文件中的各配置項之間的對應關系。如果說PropertyPlaceholderConfigurer做的這些是“明事”的話,那相對來說,PropertyOverrideConfigurer所做的可能就有點兒“神不知鬼不覺”了。

????????可以通過PropertyOverrideConfigurer對容器中配置的任何你想處理的bean定義的property信息進行覆蓋替換。這聽起來比較抽象,我們還是給個例子吧!比如之前的dataSource定義中,maxActive的值為100,如果我們覺得100不合適,那么可以通過PropertyOverrideConfigurer在其相應的properties文件中做如下所示配置,把100這個值給覆蓋掉,如將其配置為200:

dataSource.maxActive=200

????????這樣,當容器實例化對象的時候,該dataSource對象對應的maxActive值就是200,而不是原來 XML配置中的100。也就是說,PropertyOverrideConfigurer的properties文件中的配置項,覆蓋掉了原來XML中的bean定義的property信息。但這樣的活動,只看XML配置的話,你根本看不出哪個bean定義的哪個property會被覆蓋替換掉,只有查看PropertyOverrideConfigurer指定的properties配置文件才會了解。基本上,這種覆蓋替換對于bean定義來說是透明的。

3. CustomEditorConfigurer

? ????????其他兩個BeanFactoryPostProcessor都是通過對BeanDefinition中的數據進行變更以達到某? 種目的。與它們有所不同,CustomEditorConfigurer是另一種類型的BeanFactoryPostProcessor實現,它只是輔助性地將后期會用到的信息注冊到容器,對BeanDefinition沒有做任何變動。

? ??????我們知道,不管對象是什么類型,也不管這些對象所聲明的依賴對象是什么類型,通常都是通過XML(或者properties甚至其他媒介)文件格式來配置這些對象類型。但XML所記載的,都是String類型,即容器從XML格式的文件中讀取的都是字符串形式,最終應用程序卻是由各種類型的對象所構成。要想完成這種由字符串到具體對象的轉換(不管這個轉換工作最終由誰來做),都需要這種轉換規則相關的信息,而CustomEditorConfigurer就是幫助我們傳達類似信息的。

? 自定義PropertyEditor

4.4.3 了解Bean的一生

????????容器啟動之后,并不會馬上就實例化相應的bean定義。我們知道,容器現在僅僅擁有所有對象的BeanDefinition來保存實例化階段將要用的必要信息。只有當請求方通過BeanFactory的getBean()方法來請求某個對象實例的時候,才有可能觸發Bean實例化階段的活動。BeanFactory的getBe法可以被客戶端對象顯式調用,也可以在容器內部隱式地被調用。隱式調用有如下兩種情況。

? 對于BeanFactory來說,對象實例化默認采用延遲初始化。通常情況下,當對象A被請求而需要第一次實例化的時候,如果它所依賴的對象B之前同樣沒有被實例化,那么容器會先實例化對象A所依賴的對象。這時容器內部就會首先實例化對象B,以及對象 A依賴的其他還沒有實例化的對象。這種情況是容器內部調用getBean(),對于本次請求的請求方是隱式的。

? ApplicationContext啟動之后會實例化所有的bean定義,這個特性在本書中已經多次提到。但ApplicationContext在實現的過程中依然遵循Spring容器實現流程的兩個階段,只不過它會在啟動階段的活動完成之后,緊接著調用注冊到該容器的所有bean定義的實例化方法getBean()。這就是為什么當你得到ApplicationContext類型的容器引用時,容器內所有對象已經被全部實例化完成。不信你查一下類org.AbstractApplicationContext的refresh()方法。

? ??????之所以說getBean()方法是有可能觸發Bean實例化階段的活動,是因為只有當對應某個bean定義的getBean()方法第一次被調用時,不管是顯式的還是隱式的,Bean實例化階段的活動才會被觸發,第二次被調用則會直接返回容器緩存的第一次實例化完的對象實例(prototype類型bean除外)。當getBean()方法內部發現該bean定義之前還沒有被實例化之后,會通過createBean()方法來進行具體的對象實例化,實例化過程如圖4-10所示

????????Spring容器將對其所管理的對象全部給予統一的生命周期管理,這些被管理的對象完全擺脫了原來那種“new完后被使用,脫離作用域后即被回收”的命運。下面我們將詳細看一看現在的每個bean在容器中是如何走過其一生的。

1. Bean的實例化與BeanWrapper

????????容器在內部實現的時候,采用“策略模式(Strategy Pattern)”來決定采用何種方式初始化bean實例。通常,可以通過反射或者CGLIB動態字節碼生成來初始化相應的bean實例或者動態生成其子類。

????????org.springframework.beans.factory.support.InstantiationStrategy定義是實例化策略的抽象接口,其直接子類SimpleInstantiationStrategy實現了簡單的對象實例化功能,可以通過反射來實例化對象實例,但不支持方法注入方式的對象實例化。CglibSubclassingInstantiation-Strategy繼承了SimpleInstantiationStrategy的以反射方式實例化對象的功能,并且通過CGLIB的動態字節碼生成功能,該策略實現類可以動態生成某個類的子類,進而滿足了方法注入所需的對象實例化需求。默認情況下,容器內部采用的是CglibSubclassingInstantiationStrategy。

????????容器只要根據相應bean定義的BeanDefintion取得實例化信息,結合CglibSubclassingInstantiationStrategy以及不同的bean定義類型,就可以返回實例化完成的對象實例。但是,返回方式上有些“點綴”。不是直接返回構造完成的對象實例,而是以BeanWrapper對構造完成的對象實例進行包裹,返回相應的BeanWrapper實例。至此,第一步結束。

????????BeanWrapper接口通常在Spring框架內部使用,它有一個實現類org.springframework.beans.BeanWrapperImpl。其作用是對某個bean進行“包裹”,然后對這個“包裹”的bean進行操作,比如設置或者獲取bean的相應屬性值。而在第一步結束后返回BeanWrapper實例而不是原先的對象實例,就是為了第二步“設置對象屬性”。

????????BeanWrapper定義繼承了org.springframework.beans.PropertyAccessor接口,可以以統一的方式對對象屬性進行訪問;BeanWrapper定義同時又直接或者間接繼承了PropertyEditorRegistry和TypeConverter接口。不知你是否還記得CustomEditorConfigurer?當把各種PropertyEditor注冊給容器時,知道后面誰用到這些PropertyEditor嗎?對,就是BeanWrapper!在第一步構造完成對象之后,Spring會根據對象實例構造一個BeanWrapperImpl實例,然后將之前CustomEditor-Configurer注冊的PropertyEditor復制一份給BeanWrapperImpl實例(這就是BeanWrapper同時又是PropertyEditorRegistry的原因)。這樣,當BeanWrapper轉換類型、設置對象屬性值時,就不會無從下手了。

2. 各色的Aware接口

????????當對象實例化完成并且相關屬性以及依賴設置完成之后,Spring容器會檢查當前對象實例是否實現了一系列的以Aware命名結尾的接口定義。如果是,則將這些Aware接口定義中規定的依賴注入給當前對象實例。

3. BeanPostProcessor

????????BeanPostProcessor的概念容易與BeanFactoryPostProcessor的概念混淆。但只要記住Bean-PostProcessor是存在于對象實例化階段,而BeanFactoryPostProcessor則是存在于容器啟動階段,這兩個概念就比較容易區分了。與BeanFactoryPostProcessor通常會處理容器內所有符合條件的BeanDefinition類似,Bean-PostProcessor會處理容器內所有符合條件的實例化后的對象實例。

4. InitializingBean和init-method

? ??????org.springframework.beans.factory.InitializingBean是容器內部廣泛使用的一個對象生命周期標識接口。

? ??????該接口定義很簡單,其作用在于,在對象實例化過程調用過“BeanPostProcessor的前置處理”之后,會接著檢測當前對象是否實現了InitializingBean接口,如果是,則會調用其afterPropertiesSet()方法進一步調整對象實例的狀態。比如,在有些情況下,某個業務對象實例化完成后,還不能處于可以使用狀態。這個時候就可以讓該業務對象實現該接口,并在方法afterPropertiesSet()中完成對該業務對象的后續處理。

5. DisposableBean與destroy-method

????????當所有的一切,該設置的設置,該注入的注入,該調用的調用完成之后,容器將檢查singleton類型的bean實例,看其是否實現了org.springframework.beans.factory.DisposableBean接口。或者其對應的bean定義是否通過的destroy-method屬性指定了自定義的對象銷毀方法。如果是,就會為該實例注冊一個用于對象銷毀的回調(Callback),以便在這些singleton類型的對象實例銷毀之前,執行銷毀邏輯。

????????與InitializingBean和init-method用于對象的自定義初始化相對應,DisposableBean和destroy-method為對象提供了執行自定義銷毀邏輯的機會。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容