3.1.簡介 在Spring中,兩個最基本最重要的包是:org.springframework.beans和org.springframework.context包。這兩個包中的代碼爲Spring的反向控制特性(也叫作依賴注射)提供了基礎。BeanFactory[http://www.springframework.org/docs/api/org/ springframework/beans/factory/BeanFactory.html]提供了能夠管理任何種類beans(對象)的先進的配置機制,潛在地利用任何一種存儲設備。ApplicationContext建立在BeanFactory之上並增加了其他的功能,比如同Spring AOP特性更容易整合,信息資源處理(用於國際化),事件傳播,聲明式機制用於創建ApplicationContext和可選的父上下文以及與應用層相關的上下文(比如WebApplicationContext),以及其他的增強。 簡而言之,BeanFactory提供了配置框架和基本的功能,而ApplicationContext爲它增加了更強的功能,這些功能中的一些或許更加J2EE和企業中心(enterprise-centric)。一般來說,ApplicationContext是BeanFactory的完全超集,任何BeanFactory功能和行爲的描述也同樣被認爲適用於ApplicationContext。 用戶有時在特定的場合下不確定BeanFactory和ApplicationContext哪一個更適於使用。通常大部分在J2EE環境中構建的應用最好的選擇是使用ApplicationContext,因爲它不僅提供了BeanFactory所有的特性以及它自己附加的特性,而且還提供更聲明化的方法去使用一些功能,這通常是令人滿意的。你最好選擇BeanFactory的主要場景通常是當內存使用是最主要的關注(比如在一個每kb都要計算的applet中),而且你也不需要ApplicationContext所有特性的時候。 這一章粗略地分爲兩部分,第一部分包括同時適用於BeanFactory和ApplicationContext的基本原則。第二部分會包括僅僅適用於ApplicationContext的一些特性。 3.2.BeanFactory和Bean定義(基礎) 3.2.1.BeanFactory BeanFactory是實際上實例化,配置和管理許多beans的容器。這些beans通常互相之間合作,因而也在它們之間產生依賴。這些依賴反映在被BeanFactory使用的配置數據中(一些依賴可能不像配置數據一樣可見,而更可能在運行期作爲bean之間程序交互的函數)。 一個BeanFactory用接口org.springframework.beans.factory.BeanFactory表示,這個接口有多個實現。最常使用的的簡單的BeanFactory實現是org.springframework.beans.factory.xml.XmlBeanFactory。(這帶有如下的暗示:ApplicationContext是BeanFactory的子類,所以大多數的用戶更喜歡使用ApplicationContext的XML形式)。 儘管對於大多數場景,幾乎所有的被BeanFactory管理的用戶代碼不需要知道BeanFactory,但是BeanFactory還是不得不實例化。這一步可以通過如下清楚的用戶代碼發生: InputStream is = new FileInputStream("beans.xml"); XmlBeanFactory factory = new XmlBeanFactory(is); 或者: ClassPathResource res = new ClassPathResource("beans.xml"); XmlBeanFactory factory = new XmlBeanFactory(res); 或者: ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext( new String[] {"applicationContext.xml", "applicationContext-part2.xml"}); // of course, an ApplicationContext is just a BeanFactory BeanFactory factory = (BeanFactory) appContext; 對於很多的應用場景,用戶代碼不需要實例化BeanFactory,因爲Spring框架代碼會做這件事。舉例來說,web層提供支持代碼,用來自動讀取一個Spring ApplicationContext並把讀取過程作爲一個J2EE web應用啓動過程的一部分。這個聲明式的過程在這裏描述: 編程式地操縱BeanFactory將會在後面接受,下面部分將集中描述BeanFactory的配置。 在最基礎的級別上,一個BeanFactory配置由一個或多個BeanFactory必須管理的Bean的定義組成。在一個XmlBeanFactory中,在頂級的<beans>元素中配置一個或多個<bean>元素。
3.2.2.Bean定義 一個XmlBeanFactory實體中的Bean定義包括如下的細節以及其他一些信息: l 一個classname:通常這是Bean定義中描述的那個bean的真正的實現類。然而如果一個bean使用一個靜態工廠方法所創建而不是被普通的構造函數創建,那麼這裏實際上就是工廠類的classname。 l Bean行爲配置元素:聲明這個bean在容器的行爲方式(比如prototype或singleton,自動裝配模式,依賴檢查模式,初始化和析構方法)。 l 構造函數參數和需要賦予新創建bean的屬性:舉一個例子,一個管理連接池的bean使用的連接數目(即可以指定爲一個property也可以作爲一個構造函數參數),或者池大小的限制數目。 l 這個bean需要起作用的其他beans:比如它的合作者(同業也可以作爲屬性或者構造函數的參數)。這個也被叫做依賴。 上面列出的概念直接轉化爲組成bean定義的一組元素。這些元素在下面的表格中列出,關於它們每一個的更詳細的說明都有一個鏈接相對應。 表3.1。Bean定義說明 特性 更多信息 class 3.2.3節,bean class id和name 3.2.4節,bean標誌符(id和name) singleton或prototype 3.2.5節,使用singleton還是不使用呢 構造函數參數 3.3.1節,設置bean的屬性和合作者 bean屬性 3.3.1節,設置bean的屬性和合作者 自動裝配模式 3.3.5節,自動裝配合作者 依賴檢查模式 3.3.6節,檢查依賴 初始化方法 3.4.1節,生命週期接口 析構方法 3.4.1節,生命週期接口 注意bean定義是由真實的接口org.springframework.beans.factory.config.BeanDefinition以及它的各種子接口和實現所表示的。然而,絕大多數的用戶代碼不需要與BeanDefination直接作用。 3.2.3.bean class class屬性通常是強制性的(參看3.2.3.3節-通過實例工廠方法創建bean和3.5節-child bean的定義這兩個例外情況),被用來達到兩個目的中的一個。在絕大多數普通的場景中BeanFactory自己直接調用bean的構造函數來創建bean(相當於調用new的Java代碼),class屬性指定了需要創建的bean的類。在比較少的普通場景中BeanFactory調用某個類的靜態的所謂的工廠方法來創建bean,class屬性指定了實際包含靜態工廠方法的那個類。(至於靜態工廠方法返回的bean的類型是同一個類還是完全不同的另一個類,這並不重要)。 3.2.3.1.通過構造方法創建bean 當使用構造函數方式創建bean的時候,所有普通的類都可以被Spring使用以及同Spring兼容。這就是說,被創建的類不需要實現任何指定的接口或者按照特定的樣式進行編碼。僅僅指定bean的類就足夠了。然而,由於依賴你給特定bean使用的IoC類型,你可能需要一個默認的(空的)構造函數。 另外,BeanFactory並不侷限於管理真正的JavaBeans,事實上它也能夠管理任何你想讓它幫你管理的類。雖然很多使用Spring的人喜歡在BeanFactory中用真正的JavaBeans(僅包含一個默認的(無參數的)構造方法以及在屬性後面包含適當的相對應的setter和getter),但是在你的BeanFactory中也可以使用特殊的非bean樣式的類。舉例來說,如果你需要使用一個遺留下來的完全沒有遵守JavaBean規範的連接池,不要擔心,Spring同樣能夠管理它。 使用XmlBeanFactory你可以像如下這樣制定你的bean class:
至於爲構造方法提供參數(可選的),以及對象實例構造後設置實例的屬性,將會在後面敘述。 3.2.2.2.通過靜態工廠方法創建Bean 當你定義一個使用靜態工廠方法創建的bean,同時使用class屬性指定包含靜態工廠方法的類,這個時候就需要另一個叫做factory-method的屬性來指定工廠方法的名字。Spring期待能夠調用這個方法(包含可選的一組參數將在後面敘述)並且返回一個有效的對象,之後這個對象就會如同用構造方法創建的對象一樣被看待。用戶可以使用這樣的bean定義在遺留代碼中調用靜態工廠。 下面是一個bean定義的例子,聲明瞭一個bean要通過調用一個工廠方法被創建。要注意定義並沒有指定返回對象的類型,只指定包含工廠方法的類。在這個例子中,createInstance必須是靜態方法。
至於爲工廠方法提供參數(可選的),以及對象實例被工廠方法返回後設置該實例的屬性,將會在後面敘述。 3.2.3.3.通過實例工廠方法創建bean 與使用靜態工廠方法創建bean非常類似的是,使用一個實例工廠方法(非靜態的),通過調用一個已存在bean(這個bean應該是工廠類型)的工廠方法來創建新的bean。 要使用這種機制,class屬性必須保留爲空的,而且factory-bean屬性必須指定一個包含工廠方法的bean的name(¥¥)。而工廠方法本身仍然要通過factory-method屬性設置。 下面是一個例子:
設置bean屬性的機制將在後面討論,這個方案有一個暗示就是factory bean本身能夠被容器通過依賴注射來管理和配置。 3.2.4.bean的標誌符(id和name) 每一個bean都有一個或多個ids(也叫作標誌符,或names;這些名詞說的是一回事)。這些id在bean被管理的BeanFactory或ApplicationContext中必須是唯一的。一個bean差不多總是隻有一個id,但是如果一個bean有超過一個的id,那麼另外的那些本質上可以認爲是別名。在一個XmlBeanFactory中(包括ApplicationContext的各種形式),你可以用id或者name屬性來指定bean的id(s),並且在這兩個或其中一個屬性中至少指定一個id。id屬性允許你指定一個id,並且它在XML DTD(定義文檔)中作爲一個真正的XML元素的ID屬性被標記,所以parser能夠在其他元素指回向它的時候做一些額外的校驗。正因如此,用id屬性指定bean的id是一個更好的方式。然而,XML規範嚴格限定了在XML ID中合法的字符。通常這並不是一個實在的約束,但是如果你有必要使用這些字符(在ID中的非法字符)中的一個,或者你想給bean引進其他的別名,那麼你可以通過name屬性指定一個或多個ids(用逗號“,”或者分號“;”分開)。 3.2.5.使用singleton還是不使用呢 Beans被定義爲兩種部署模式中的一種:singleton或non-singleton。(後一種也別叫作prototype,儘管這個名詞用的不精確因爲它並不是非常適合)。如果一個bean是singleton的,那麼僅有一個共享的這個bean的實例被管理,所有id或者ids與這個bean定義匹配的beans請求都會導致那個唯一特定的bean實例被返回。 而non-singleton,prototype模式的bean部署產生的後果是:對這個bean的每次請求都會創建一個新的bean實例。這對於例如每個user需要一個獨立的user對象這樣的情況是非常理想的。 Beans默認被部署爲singleton模式,除非你指定爲另外的。要緊記把類型變爲non-singletion(prototype)後,每一次對這個bean的請求都會導致一個新創建的bean而且這可能並不是你真正想要的。所以僅僅在絕對需要的時候才把模式改成prototype的。 在下面這個例子中,兩個bean一個被定義爲singleton的而另一個被定義爲non-singleton(prototype)的。exampleBean會在客戶端每次向BeanFactory請求它的時候被創建,而yetAnotherExample僅僅被創建一次;在對它每次請求的時候這個實例的引用會被返回。
注意:當部署一個bean爲prototype模式時,這個bean的生命週期就會有稍許改變。通過定義,Spring無法再管理一個non-singleton/prototype bean的整個生命週期,因爲當它創建之後,它被交給客戶端而且容器根本不再留意它了。當說起non-singleton/prototype bean的時候,你可以把Spring的角色想象成“new”操作符的替代品。從那之後的任何生命週期方面的事情都由客戶端來處理。BeanFactory中bean的生命週期將會在3.4.1節,生命週期接口一節中有更詳細的敘述。 3.3.屬性,合作者,自動裝配和依賴檢查 3.3.1.設置bean的屬性和合作者 反向控制已經作爲依賴注射被提及到。這條基本的規則是bean通過以下方式來定義它們的依賴(比如它們與之合作的其他對象):構造函數的參數,工廠方法的參數;當對象實例被構造出來或從一個工廠方法返回後設置在這個實例上的屬性。容器的工作就是創建完bean之後,真正地注射這些依賴。This is fundamentally the inverse (hence the name Inversion of Control) of the bean instantiating or locating its dependencies on its own using direct construction of classes,就像ServiceLocator模式一樣。我們不會詳細闡述依賴注射的優點,很顯然通過使用它:代碼變得非常清晰;當beans不再自己查找他們的依賴而是由容器提供,甚至不需要知道在何處定位依賴以及依賴實際上是什麼類型,這時高層次的解耦也變得很容易了。 正如上面提到的那樣,反向控制/依賴注射存在兩種主要的形式: l 基於setter的依賴注射,是在調用無參的構造函數或無參的靜態工廠方法實例化你的bean之後,通過在你的bean上調用setter實現的。在BeanFactory中定義的使用基於setter的注射依賴的bean是真正的JavaBeans。Spring一般提倡使用基於setter的依賴注射,因爲很多的構造函數參數將會是笨重的,尤其在有些屬性是可選的情況下。 l 基於構造函數的注射依賴,它是通過調用帶有許多參數的構造方法實現的,每個參數表示一個合作者或者屬性。另外,調用帶有特定參數靜態工廠方法來構造bean可以被認爲差不多等同的,接下來的文字會把構造函數的參數和靜態工廠方法的參數看作一樣的。雖然Spring一般提倡在大多數情況下使用基於setter的依賴注射,但是Spring還是完全支持基於構造函數的依賴注射,因爲你可能想要把它用在那些只提供多參數構造函數並且沒有setter的遺留的beans上。另外對於一些比較簡單的beans,一些人更喜歡使用構造函數方法以確保beans不會被以錯誤的形式構造。 BeanFactory同時支持這兩種將依賴注射到被管理bean中的方式。(實際上它還支持在一些依賴已經通過構造函數方法注射後再使用基於setter的依賴注射)。依賴的配置是以BeanDefinition的形式出現,它和JavaBeans的PropertyEditors一起使用來了解如何把properties從一個格式轉變爲另一個。真正傳送的值被變爲Propertyvalue對象的形式。然而,大多數Spring的使用者並不要直接(比如編程式地)處理這些classes,而更多地使用一個XML定義文件,這個文件會在內部被轉變爲這些classes的實例,用來讀取整個BeanFactory或ApplicationContext。 Bean依賴的決定通常按照下面所示發生: l BeanFactory通過使用一個描述所有beans的配置被創建和實例化。大多數的Spring用戶使用一個支持XML格式配置文件的BeanFactory或ApplicationContext變體。 l 每一個bean的依賴都是屬性的形式,構造函數參數的形式,或者當用靜態工廠方法代替普通構造函數時工廠方法的參數。這些依賴將會在bean真正被創建出來後提供給bean。 l 每一個屬性或者構造函數參數要麼是一個要被設置的值的真實定義,要麼是一個指向BeanFactory中其他bean的引用。在ApplicationContext的情況下,這個引用可以指向一個父親ApplicationContext中bean。 l 每一個是真實值的屬性或構造函數參數,必須能夠從(配置文件中)被指定的格式轉變爲屬性或構造函數參數的真實類型。默認的Spring能夠把一個字符串格式的值轉變爲所有內建的類型,比如int, long, String, boolean等等。另外當說到基於XML的BeanFactory變體的時候(包括ApplicationContext變體),它們已經爲定義Lists, Maps, Sets和Properties集合類型提供了內在的支持。另外,Spring通過使用JavaBeans的PropertyEditor定義從而能夠將字符串值轉變爲其他任意的類型。(你可以爲BeanFactory提供你自己的PropertyEditor定義從而能夠轉變你自定義的類型;更多關於PropertyEditors的信息以及如何手動增加自定義的PropertyEditors請參看3.9節,註冊附加的自定義PropertyEditors)。當一個bean屬性是一個Java Class類型,Spring允許你用這個class的名字的字符串作爲這個屬性的值,ClassEditor這個內建的PropertyEditor會幫你把class的名字轉變成真實的Class實例。 l 很重要的一點就是要了解:Srping在BeanFactory創建的時候要校驗BeanFactory中每一個Bean的配置。這些校驗包括作爲Bean引用的屬性必須實際引用一個合法的beans(比如被引用的bean也定義在BeanFactory中,或者當ApplicationContext時,在父親上下文中)。但是,bean屬性本身直到bean被真實建立的時候才被設置。對於那些是singleton並且被設置爲pre-instantiated的bean來說(比如一個ApplicationContext中的singleton bean),Bean創建發生在BeanFactory被創建的時候,但是對於其他情況,發生在bean被請求的時候。當一個bean必須被創建時,它會潛在地導致一系列的其他bean被創建,像它的依賴以及它的依賴的依賴(如此下去)被創建和賦值。 l 通常你可以信任Spring做了正確的事情。它會在BeanFactory裝載的時候檢查處錯誤,包括對不存在bean的引用和循環引用。它會儘可能晚地真正設置屬性和解決依賴(比如創建那些需要的依賴),在bean真正被創建的時候。這就意味着:就算一個BeanFactory被正確地裝載,稍後當你請求一個bean的時候,如果創建那個bean或者它的依賴的時候出現了錯誤,這個BeanFactory也會生成一個異常。比如,如果一個bean拋出一個異常作爲缺少或非法屬性的結果,這樣的情況就會發生。這種潛在地推遲一些配置錯誤可見性的行爲正是ApplicationContext默認預實例化singleton beans的原因。以前期的時間和內存爲代價在beans真正需要之前創建它們,你就可以在ApplicationContext創建的時候找出配置錯誤,而不是在後來。如果你願意,你也可以覆蓋這種默認的行爲,設置這些singleton bean爲lazy-load(不是預實例化的)。 一些例子: 首先,一個使用BeanFactory以及基於setter的依賴注射。下面是使一個定義一些bean的XmlBeanFactory配置文件的一小部分。接下去是正式的主bean的代碼,演示了適當的setter聲明。
正如你所看到的一樣,setter被聲明得與XML文件中指定的屬性相對應。(XML文件中的屬性,直接對應着RootBeanDefinition中的Propertyvalues對象) 接着是一個使用IoC type3(基於構造函數的依賴注射)的BeanFactory。下面是XML配置中的一段,指定了構造函數參數以及展示構造函數的代碼:
正如你所看到的,bean定義中指定的構造函數參數將會作爲ExampleBean的構造函數參數被傳入。現在考慮一下替換掉構造函數的變體,Spring被告知調用一個靜態工廠方法來返回一個對象的實例:
需要注意的是:靜態工廠方法通過constructor-arg元素被支持,構造函數事實上也是一樣的被使用。這些參數是可選的。很重要的一點是工廠方法所返回的class類型不一定要和包含着個靜態工廠方法的class一致,雖然上面這個例子中是一樣的。前面所提到的實例工廠方法(non-static)用法基本上是一樣的(除了使用factory-bean屬性代替class屬性),在這裏就不再詳細敘述了。 3.3.2.深入Bean屬性和構造函數參數 正如前面提到的那樣,bean的屬性和構造函數參數可以被定義爲其他managed beans的引用(合作者),或者內聯定義的值。爲了達到這個目的,XmlBeanFactory在property和constructor-arg元素中支持許多子元素類型。 value元素用人可讀的字符串形式指定屬性或構造函數參數。正如前面提到的那樣,JavaBeans的PropertyEditors被用來將這些字符串值從java.lang.String類型轉變爲真實的屬性或參數類型。
null元素別用來處理null值。Spring將porperties等的空參數視爲空的字符串。下面這個XmlBeanFactory配置: <bean class="ExampleBean"> <property name="email"><value></value></property> </bean> 導致email屬性被設置爲””,同java代碼:exampleBean.setEmail(“”)等價。而專門的<null>元素則可以用來指定一個null值,所以:
同代碼:exampleBean.setEmail(null)是等價的。 list, set, map和props元素可以用來定義和設置類型爲Java的List, Set, Map和Properties的屬性和參數。
注意:Map的entry或set的value,它們的值又可以是下面元素中的任何一個: ( bean | ref | idref | list | set | map | props | value | null ) 在property元素中的bean元素用來定義一個內聯的bean,代替指向BeanFactory其他地方定義的bean的應用。內聯bean定義不需要任何id定義。
idref元素完全是一種簡寫和防止錯誤的方式,用容器中其他bean的id 或name的字符串來設置屬性。
這個在運行的時候同下面的片段一致:
第一種形式比第二種形式更好的原因是:使用idref標記將會使Spring在部署的時候就驗證其他的bean是否真正存在;在第二種形式中,targetName屬性的class僅僅會在這個class被Spring實例化的時候做它自己的驗證,很可能在容器真正部署完很久之後。 另外,如果被引用的bean在同一個xml文件中而且bean的名稱是bean的id,那麼local屬性就可以被使用。它會讓XML parser自己更早地驗證bean的名稱,在XML文檔解析的時候。
ref元素是最後一個能在property元素中使用的元素。它是用來給一些特定的屬性設值的,這些屬性是容器管理的其他bean的引用(可以叫做合作者)。正如前一節提到的,被引用的bean被認爲是那些屬性要被設置的bean的依賴,被應用的bean將會在屬性設置之前需要時(如果是一個singleton bean可能已經被容器初始化),一經請求就被初始化。所有的引用根本上就只是一個指向其他對象的引用,不過這裏其它對象的id/name如何被指定有3種形式,這3種不同形式決定scoping和驗證如何被處理。 用ref標籤的bean屬性指定目標bean是最常見的形式,對於同一個BeanFactory/ ApplicationContext(無論是否在同一個XML文件中)中,或者parent BeanFactory/ ApplicationContext中的任何bean,允許創建指向它的bean。bean屬性的值可以同目標bean的id屬性相同,也可以同目標bean的name屬性中任何一個值相同。 <ref bean="someBean"/> 用local屬性指定目標bean可以利用XML parser的能力在同一個文件中驗證XML id引用。local屬性的值必須與目標bean的id屬性一致。如果在同一個文件中沒有匹配的元素,XML parser將會產生一個錯誤。因此,如果目標bean在同一個XML文件中,那麼使用local形式將是最好的選擇(爲了能夠儘可能早的發現錯誤)。 <ref local="someBean"/> 用parent屬性指定目標bean允許一個引用指向當前BeanFactory(ApplicationContext)的parent BeanFactory(ApplicationContext)中的bean。Parent屬性的值可以同目標bean的id屬性相同,也可以同目標bean的name屬性中的一個值相同,而且目標bean必須在當前BeanFactory(ApplicationContext)的parent中。當需要用某些proxy包裝一個parent上下文中存在的bean(可能和parent中的有同樣的name),所以需要原始的對象用來包裝它。 <ref parent="someBean"/> 3.3.3.方法注射 對於大部分的用戶來說,容器中多數的beans將會是singletons。當一個singleton bean需要同另一個singleton bean合作(使用),或者一個non-singleton bean需要同另一個non-singleton bean合作的時候,通過定義一個bean爲另一個bean的屬性來處理這種依賴的做法是足以勝任的。然而當bean的生命週期不同的時候就有一個問題。想想一下一個singleton bean A,或許在每次方法調用的時候都需要使用一個non-singleton bean B。容器僅僅會創建這個singleton bean A一次,因此僅僅有一次的機會去設置它的屬性。容器沒有機會每次去爲bean A提供新的bean B的實例。 一個解決這個問題的方法是放棄一些反向控制。Bean A可以通過實現BeanFactoryAware知道容器的存在(參見這裏),使用編程的手段(參見這裏)在需要的時候通過一個getBean(“B”)調用來向容器請求新的bean B實例。因爲bean的代碼知道Spring並且耦合於Spring,所以這通常不是一個好的方案。 BeanFactory的高級特性:方法注射可以以清潔的方式處理這個場景以及其他一些場景。 3.3.3.1.Lookup方法注射 Lookup方法注射使用到了容器能夠重寫容器中bean的抽象或具體方法的能力,返回對容器中其他bean的查找的結果。被查找的bean在上面描述的場景中通常是一個non-singleton bean(儘管也可以是一個singleton的)。Spring通過使用CGLIB庫在客戶端的class之上修改二進制碼,從而實現上述的場景要求。 在客戶端的class包含需要注入的方法,方法定義必須是如下形式的抽象(具體)定義: protected abstract SingleShotHelper createSingleShotHelper(); 如果方法不是抽象的,Spring就會直接重寫已有的實現。在XmlBeanFactory的情況下,你可以使用bean定義中的lookup-method屬性來指示Spring去注射/重寫這個方法以便從容器返回一個特定的bean。舉個例子:
當myBean需要一個新的singleShotHelper的實例的時候,它就會調用它自己的createSingleShotHelper方法。值得注意的是:部署beans的人員必須小心地將singleShotHelper作爲一個non-singleton部署(如果確實需要這麼做)。如果它作爲一個singleton(不管是明確指出的還是默認的)而部署,同一個singleShotHelper實例將會每次被返回。 Lookup方法注射能夠同構造函數注射結合(對創建的bean提供可選的構造函數參數),也可以同setter注射結合(在創建的bean之上設置屬性)。 3.3.3.2.任意方法的替換 沒有lookup方法注射那麼有用的另一種方法注射的形式是,用另外的方法實現替換掉被管理bean的任意的方法。用戶可以安全地跳過這一節(敘述這個有些高級的特性),除非這個功能確實需要。 在一個XmlBeanFactory中,對於一個被部署的bean,replaced-method元素可以用來把已存在的方法實現替換爲其他的實現。考慮如下的class,有一個我們想要重寫的computevalue方法:
需要爲新方法定義提供實現org.springframework.beans.factory.support.MethodReplacer接口的類。 [code]/** meant to be used to override the existing computevalue implementation in MyvalueCalculator */ public class ReplacementComputevalue implements MethodReplacer { public Object reimplement(Object o, Method m, Object[] args) throws Throwable { // get the input value, work with it, and return a computed result String input = (String) args[0]; ... return ...; }[/code] 部署原始class和指定方法重寫的BeanFactory部署定義象下面所示的:
replaced-method元素中的一個或多個arg-type元素用來暗示,這個方法的方法簽名被重載了。注意,參數的簽名只有在方法被重載以及有多個不同的形式的時候才真正需要。爲了方便,參數的類型字符串可以使全限定名的子字符串。比如,以下的都匹配java.lang.String。 java.lang.String String Str 因爲參數的個數通常就足夠區別不同的可能,所以僅僅使用匹配參數的最短的字符串能夠節省很多鍵入工作。 3.3.4.使用depends-on 對於大多數的情況,一個bean是其他bean的一個依賴這個事實,是由這個bean是否被當作其他bean的屬性而設置表達。在XmlBeanFactory中,它是通過ref元素來完成的。與這種方式不同的是,有時一個知道container的bean僅僅會被給與它的依賴的id(使用一個字符串值或等價的idref元素)。接着第一個bean就編程式地向容器請求它的依賴。在兩種情況下,依賴都會在依賴它的bean之前被恰當地初始化。 對於相對罕見的情況,beans之間的依賴不夠直接(舉例,當一個類中的靜態初始塊需要被觸發,比如數據庫驅動的註冊),depends-on元素可以用來在使用這個元素的bean初始化之前明確強制一個或多個beans初始化。 下面是一個配置的例子:
3.3.5.自動裝配合作者 BeanFactory能夠自動裝配合作bean之間的關係。這就意味着,讓Spring通過檢查BeanFactory的內容來自動搞定你的bean的合作者(其他的bean),這是有可能的。自動裝配功能有5種模式。自動裝配時指定給每一個bean的,因此可以給一些bean使用而其他的bean不自動裝配。通過使用自動裝配,減少(或消除)指定屬性(或構造函數參數)的需要,節省顯著的鍵入工作是有可能的。在XmlBeanFactory中,使用bean元素的autowire屬性來指定bean定義的自動裝配模式。以下是允許的值: 表3.2。自動裝配模式 模式 解 釋 no 不使用自動裝配。Bean的引用必須通過ref元素定義。這是默認的配置,在較大的部署環境中不鼓勵改變這個配置,因爲明確的指定合作者能夠得到更多的控制和清晰性。從某種程度上說,這也是系統結構的文檔形式。 byName 通過屬性名字進行自動裝配。這個選項會會檢查BeanFactory,查找一個與將要裝配的屬性同樣名字的bean 。比如,你有一個bean的定義被設置爲通過名字自動裝配,它包含一個master屬性(也就是說,它有一個setMaster()方法),Spring就會查找一個叫做master的bean定義,然後用它來設置master屬性。 byType 如果BeanFactory中正好有一個同屬性類型一樣的bean,就自動裝配這個屬性。如果有多於一個的情況,則一個致命的異常會被拋出,它指出你可能不能對那個bean使用byType的自動裝配。如果沒有匹配的bean,則什麼都不會發生,屬性不會被設置。如果這是你不想要的情況(什麼都不發生),通過設置dependency-check=”objects”屬性的值來指定在這種情況下應該拋出錯誤。 constructor 這個同byType類似,不過是應用於構造函數參數的。如果在BeanFactory中不是恰好有一個bean與構造函數參數相同類型,則一個致命的錯誤會產生。 autodetect 通過對bean class的內省來選擇constructor或byType。如果找到一個缺省的構造函數,那麼就會應用byType。 注意:顯式的依賴,比如property和construtor-arg元素,總會覆蓋自動裝配。自動裝配的行爲可以和依賴檢查結合使用,依賴檢查會在自動裝配完成後發生。 注意:正如我們已經提到過的,對於大型的應用,自動裝配不鼓勵使用,因爲它去除了你的合作類的透明性和結構。 3.3.6.檢查依賴 對於要部署到BeanFactory的bean的未解決的依賴,Spring有能力去檢查它們的存在性。這些依賴要麼是bean的JavaBean式的屬性,在bean的定義中並沒有個它們設置真實的值,要麼是通過自動裝配特性被提供。 當你想確保所有的屬性(或者某一特定類型的所有屬性)都被設置到bean上面的時候,這項特性就很有用了。當然,在很多情況下一個bean class的很多屬性都會有缺省的值,或者一些屬性並不能應用於所有的應用場景,那麼這個特性的作用就有限了 。依賴檢查能夠分別對每一個bean應用或取消應用,就像自動裝配功能一樣。缺省的是不檢查依賴。依賴檢查可以以幾種不同的模式處理。在XmlBeanFactory中,這個通過bean定義中的dependency-check屬性來指定,這個屬性有以下的值: 表3.3。依賴檢查模式 模式 解 釋 none 不進行依賴檢查。沒有指定值的bean屬性簡單地不進行設值。 simple 對基本類型和集合(除了合作者外[比如其他的bean]的所有東西)進行依賴檢查。 object 對合作者進行依賴檢查。 all 對合作者,基本類型和集合都進行依賴檢查。 3.4.自定義bean的自然特性 3.4.1.生命週期接口 Spring提供了一些標誌接口,用來改變BeanFactory中的bean的行爲。它們包括InitailizingBean和DisposableBean。實現這些接口將會導致BeanFactory爲調用前一個的afterPropertiesSet(),調用後一個destroy(),從而使得bean可以在初始化和析構後做一些特定的動作。 在內部,Spring使用BeanPostProcessors來處理它能找到的標誌接口以及調用適當的方法。如果你需要自定義的特性或者其他的Spring沒有提供的生命週期行爲,你可以實現自己的BeanPostProcessor。關於這方面更多的內容可以看這裏:3.7節,用BeanPostProcessors定製bean。 所有的生命週期的標誌接口都在下面敘述。在附錄的一節中,你可以找到相應的圖,展示了Spring如何管理beans;那些生命週期的特性如何改變你的bean的自然特性以及它們如何被管理。 3.4.1.1.InitializingBean/初始化方法 實現org.springframework.beans.factory.InitializingBean接口允許一個bean在它的所有必須的屬性被BeanFactory設置後,來執行初始化的工作。InitializingBean接口僅僅制定了一個方法: * Invoked by a BeanFactory after it has set all bean properties supplied * (and satisfied BeanFactoryAware and ApplicationContextAware). * <p>This method allows the bean instance to perform initialization only * possible when all bean properties have been set and to throw an * exception in the event of misconfiguration. * @throws Exception in the event of misconfiguration (such * as failure to set an essential property) or if initialization fails. */ void afterPropertiesSet() throws Exception; 注意:通常InitializingBean接口的使用是能夠避免的(而且不鼓勵,因爲沒有必要把代碼同Spring耦合起來)。Bean的定義支持指定一個普通的初始化方法。在使用XmlBeanFactory的場合下,可以通過init-method屬性來完成。舉例來說,下面的定義:
同下面的完全一樣,但卻不把代碼耦合於Spring:
3.4.1.2.DisposableBean/析構方法 實現org.springframework.beans.factory.DisposableBean接口允許一個bean,可以在包含它的BeanFactory銷燬的時候得到一個回調。DisposableBean也只指定了一個方法: /** * Invoked by a BeanFactory on destruction of a singleton. * @throws Exception in case of shutdown errors. * Exceptions will get logged but not rethrown to allow * other beans to release their resources too. */ void destroy() throws Exception; 注意:通常DisposableBean接口的使用能夠避免的(而且是不鼓勵的,因爲它不必要地將代碼耦合於Spring)。Bean的定義支持指定一個普通的析構方法。在XmlBeanFactory使用的場合下,它是通過destroy-method屬性完成的。舉例來說,下面的定義:
和下面的定義是一樣的,當時去不會把代碼耦合於Spring:
重要的提示:當以portotype模式部署一個bean的時候,bean的生命週期將會有少許的變化。通過定義,Spring無法再管理一個non-singleton/prototype bean的整個生命週期,因爲當它創建之後,它被交給客戶端而且容器根本不再留意它了。當說起non-singleton/prototype bean的時候,你可以把Spring的角色想象成“new”操作符的替代品。從那之後的任何生命週期方面的事情都由客戶端來處理。BeanFactory中bean的生命週期將會在3.4.1節,生命週期接口一節中有更詳細的敘述。(就是本節) 3.4.2.瞭解你是誰 3.4.2.1.BeanFactoryAware 對於實現了org.springframework.beans.factory.BeanFactoryAware接口的類,當它被BeanFactory創建後,它會擁有一個指向創建它的BeanFactory的引用。 public interface BeanFactoryAware { /** * Callback that supplies the owning factory to a bean instance. * <p>Invoked after population of normal bean properties but before an init * callback like InitializingBean's afterPropertiesSet or a custom init-method. * @param beanFactory owning BeanFactory (may not be null). * The bean can immediately call methods on the factory. * @throws BeansException in case of initialization errors * @see BeanInitializationException */ void setBeanFactory(BeanFactory beanFactory) throws BeansException; } 這允許beans可以編程式地操控創建它們的BeanFactory,既可以直接使用org.springframework.beans.factory.BeanFactory接口,也可以將引用cast爲已知的子類型從而獲得更多的功能。這個特性主要用於編程式地取得其他beans。雖然在一些場景下這個功能是有用的,但是一般來說它應該避免使用,因爲它使代碼與Spring耦合在一起,而且也不遵循反向控制的風格(合作者被當作屬性提供給beans)。 3.4.2.2.BeanNameAware 如果一個bean實現了org.springframework.beans.factory.BeanNameAware接口,並且被部署到一個BeanFactory中,那麼BeanFactory就會通過這個接口來調用bean來通知這個bean它(bean)被部署的id。這個回調發生在普通的bean屬性設置之後,初始化回調比如InitializiongBean的afterPropertiesSet方法(或者自定義的init-method)調用之前。 3.4.3.FactoryBean 接口org.springframework.beans.factory.FactoryBean將會被自己是factory的對象所實現。BeanFactory接口提供了三個方法: l Ojbect getObject():必須返回一個這個factory創建的對象的實例。這個實例可以使共享的(取決於這個工廠返回的是singleton還是prototype)。 l boolean isSingleton():必須返回true如果Factory返回singletons,否則返回false。 l Class getObjectType():返回getObject()方法返回的對象的類型,或者如果類型不是預先知道的則返回null。 3.5.child bean定義 一個bean定義可能會包含大量的配置信息,包括容器相關的信息(比如初始化方法,靜態工廠方法等等)以及構造函數參數和屬性的值。一個child bean定義是一個能夠從parent bean定義繼承配置數據的bean定義。然後它可以覆蓋一些值,或者添加一些其他需要的。使用父和子的bean定義可以節省很多的輸入工作。實際上,這就是一種模版形式。當編程式地使用一個BeanFactory,child bean定義用ChildBeanDefinitiaon類表示。大多數的用戶從來不需要在這個級別上使用它們,取而代之的是,在諸如XmlBeanFactory中聲明式地配置bean定義。在一個XmlBeanFactory的bean定義中,使用parent屬性指出一個child bean定義,而parent bean則作爲這個屬性的值。
如果child bean定義沒有指定class,一個child bean定義將使用父定義的class,當然也可以覆蓋它。在後面一種情況中,child bean的class必須同parent bean的兼容,比如它必須能夠接受父親的屬性值。 一個child bean定義將會從父親處繼承構造函數參數,屬性值以及方法,並且可以選擇地增加新的值。如果初始化方法,銷燬方法和/或靜態工廠方法被指定了,它們就會覆蓋父親相應的設置。 剩餘的設置將總是從子定義處得到:depends on, autowire mode, dependency check, singleton, lazy init。 在下面的例子中父定義並沒有指定一個class:
這個parent bean就無法自己實例化;它實際上僅僅是一個純模版或抽象bean,僅僅充當子定義的父定義。若要嘗試單獨使用這樣的parent bean(比如將它作爲其他bean的ref屬性而引用,或者直接使用這個parent bean的id調用getBean()方法),將會導致一個錯誤。同樣地,容器內部的preInstantiateSingletons方法會完全忽略這種既沒有parent也沒有class屬性的bean定義,因爲它們是不完整的。 重要的注意點:這裏並沒有辦法顯式地聲明一個bean定義爲抽象的。如果一個bean確實有一個class定義,那麼它就能夠被實例化。而且要注意XmlBeanFactory默認地將會預實例化所有的singletons。因此很重要的一點是:如果你有一個(父)bean定義指定了class屬性,而你又想僅僅把它當作模板使用,那麼你必須保證將lazy-init屬性設置爲true(或者將bean標記爲non-singleton),否則XmlBeanFactory(和其他可能的容器)將會預實例化它。 3.6.同BeanFactory交互 BeanFactory本質上不過是高級factory的藉口,有能力維護不同beans和它們依賴的註冊。BeanFactory使得你可以利用bean工廠讀取和訪問bean定義。當你想僅僅使用BeanFactory的時候,你可以象下面一樣創建一個並且讀入一些XML格式的bean定義: InputStream is = new FileInputStream("beans.xml"); XmlBeanFactory factory = new XmlBeanFactory(is); 基本上這就足夠了。使用getBean(String)你可以取得你的bean的實例。如果你將它定義爲一個singleton(缺省的)你將會得到同一個bean的引用,如果你將singleton設置爲false那麼你將會每次得到一個新的實例。在客戶端的眼裏BeanFactory是驚人的簡單。BeanFactory接口僅僅爲客戶端調用提供了5個方法: l boolean containsBean(String):如果BeanFactory包含一個與所給名稱匹配的bean定義則返回true l Object getBean(String):返回一個以所給名字註冊的bean的實例。返回一個singleton的共享的實例還是一個新創建的實例,這取決於bean在BeanFactory配置中如何被配置的。一個BeanException將會在下面兩種情況中拋出:bean沒有被找到(在這種情況下,拋出的是NoSuchBeanDefinitionException),或者在實例化和準備bean的時候發生異常 l Object getBean(String, Class):返回一個以給定名字註冊的bean。返回的bean將會被造型成給定的Class。如果bean不能被轉型,相應的異常將會被拋出(BeanNotOfRequiredTypeException)。此外getBean(String)的所有規則也同樣適用(見上面) l boolean isSingleton(String):得到以給定名字註冊的bean定義是一個singleton還是一個prototype。如果與給定名字相應的bean定義沒有被找到,將會拋出一個異常(NoSuchBeanDefinitionException) l String[] getAliases(String):如果給定的bean名字在bean定義中有別名,則返回這些別名 3.6.1.得到一個FactoryBean而不是它的產品 有些時候我們有必要向BeanFactory請求一個實際的FactoryBean實例本身,而不是它生產出來的bean。在調用BeanFactory(包括ApplicationContext)的getBean方法的時候,在傳入的參數bean id前面加一個“&”符號,就可以做到這一點。所以,對於一個給定的id爲myBean的FactoryBean,在BeanFactory上調用getBean(“myBean”)將會返回FactoryBean的產品,而調用getBean(“&myBean”)將會返回這個FactoryBean實例本身。 3.7.使用BeanPostprocessors定製bean 一個bean post-processor是一個實現了org.springframework.beans.factory.config. BeanPostProcessor的類,有兩個回調方法組成。當這樣的一個類作爲BeanFactory的post-processor而註冊時,對於這個BeanFactory創建的每一個bean實例,在任何初始化方法(afterPropertiesSet和聲明的init method)被調用之前和之後,post-processor將會從BeanFactory分別得到一個回調。post-processor可以對這個bean自由地做它想做的事情,包括完全忽略這個回調。一個bean post-processor通常用來檢查標記接口,或者做一些諸如將一個bean包裝成一個proxy的事情。一些Spring的助手類就是作爲bean post-processor而實現的。 重要的一點是要知道,BeanFactory和ApplicationContext對待bean post-processor有少許不同。一個ApplicationContext會自動監測到任何部署在它之上的實現了BeanPostProcessor接口的bean,並且把它們作爲post-processor註冊,然後factory就會在bean創建的時候恰當地調用它們。部署一個post-processor同部屬一個其他的bean並沒有什麼區別。而另一方面,當使用普通的BeanFactory的時候,bean post-processor必須通過類似下面的代碼來手動地顯示地註冊: ConfigurableBeanFactory bf = new .....; // create BeanFactory ... // now register some beans // now register any needed BeanPostProcessors MyBeanPostProcessor pp = new MyBeanPostProcessor(); bf.addBeanPostProcessor(pp); // now start using the factory ... 因爲手工的註冊不是很方便,而且ApplicationContext是BeanFactory功能上擴展,所以通常建議當需要post-processor的時候最好使用ApplicationContext的變體。 3.8.使用BeanFactoryPostprocessors定製bean factory 一個bean factory post-processor是一個實現了org.springframework.beans.factory.config. BeanFactoryPostProcessor接口的類。它將會被手動執行(BeanFactory的時候)或自動執行(ApplicationContext的時候),在BeanFactory構造出來後,對整個BeanFactory做某些類別的改變。Spring包含很多已存在的bean factory post-processor,比如PopertyResourceConfigure和PropertyPlaceHolderConfigurer(這兩個將在下面介紹),以及BeanNameAutoProxyCreator(對其他bean進行事務包裝或者用其他的proxy進行包裝,將會在後面敘述)。BeanFactoryPostProcessor也能用來添加自定義的editor(可參見第4.3.2節,“內建的PropertyEditors,轉換類型”)。 在BeanFactory中,使用BeanFactoryPostProcessor是手動的,將類似於下面: XmlBeanFactory factory=new XmlBeanFactory(new FileSystemResource("beans.xml")); // create placeholderconfigurer to bring in some property // values from a Properties file PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer(); cfg.setLocation(new FileSystemResource("jdbc.properties")); // now actually do the replacement cfg.postProcessBeanFactory(factory); ApplicationContext將會監測部署在其上的實現了BeanFactoryPostProcessor接口的bean,然後在適當的時候將它們作爲bean factory post-processor而使用。部署這些post-processor與部署其他的bean並沒有什麼區別。 因爲這個手動的步驟並不方便,而且ApplicationContext是BeanFactory的功能擴展,所以當需要使用bean factory post-processor的時候通常建議使用ApplicationContext的變體。 3.8.1.PopertyPlaceholderConfigurer PorpertyPlaceholderConfigurer作爲一個bean factory post-processor實現,它可以用來將BeanFactory定義中的屬性值放置到另一個單獨的Java Properties格式的文件中。這將使用戶不用對BeanFactory的主XML定義文件進行復雜和危險的修改,就可以定製一些基本的屬性(比如說數據庫的urls,用戶名和密碼)。 考慮一個BeanFactory定義的片斷,這裏的DataSource以及placeholder值是如下定義的: 在下面這個例子中,一個datasource被定義,並且我們會在一個外部的Porperties文件中配置一些相關屬性。在運行時,我們爲BeanFactory提供一個PorpertyPlaceholderConfigurer,它將會替換掉datasource一些屬性:
真正的值來自於另一個Properties格式的文件: jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root 如果要在BeanFactory中使用,bean factory post-processor必須手動運行: XmlBeanFactory factory = new XmlBeanFactory( new FileSystemResource("beans.xml")); PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer(); cfg.setLocation(new FileSystemResource("jdbc.properties")); cfg.postProcessBeanFactory(factory); 注意,ApplicationContext能夠自動辨認和應用在其上部署的實現了BeanFactoryPostProcessor的bean。這就意味着,當使用ApplicationContext的時候應用PropertyPlaceholderConfigurer會非常的方便。由於這個原因,建議想要使用這個或者其他bean factory postprocessor的用戶用ApplicationContext代替BeanFactroy使用。 PropertyPlaceHolderConfigurer不僅僅在你指定的Porperties文件中查找屬性,如果它在其中沒有找到你想使用的屬性它還會在Java系統properties中檢查。這個行爲能夠通過設置configurer的systemPropertiesMode屬性來定製。這個屬性有三個值,一個讓configurer總是覆蓋,一個讓它從不覆蓋,一個讓它僅在properties文件中找不到的時候覆蓋。請參考PropertiesPlaceholderConfigurer的JavaDoc獲得更多信息。 3.8.2.PorpertyOverrideConfigurer 另一個bean factory post-processor:PropertyOverrideConfigurer類似於PropertyPlaceholderConfiguer,但是與後者相比,前者的定義對於bean屬性可以有缺省值或者根本沒有值。如果相對重要的Properties文件沒有包含一個bean屬性對應的條目,那麼缺省的上下文定義將被使用。 注意:bean factory的定義並不會意識到被覆蓋,所以僅僅察看XML定義文件並不能立刻明顯地知道override configurer是否被使用了。在有多個PorpertyOverrideConfigurer對用一個bean屬性定義了不同的值的時候,最後一個將取勝(取決於覆蓋的機制)。 Properties文件的配置行應該是如下的格式: beanName.property=value 一個properties文件的例子將會是下面這樣的: dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb 如果一個BeanFactory包含了一個叫做dataSource的bean,這個bean又有driver和url屬性,那麼這個實例文件在這個BeanFactory中就可用。 3.9.註冊附加的定製PropertyEditor 當用字符串值設置bean的屬性時,BeanFactory實質上使用了標準的JavaBeans 的PropertyEditor將這些String轉換爲屬性的複雜類型。Spring預先註冊了很多定製的PropertyEditor(比如,將一個字符串表示的classname轉換成真正的Class對象)。另外,一個class的PropertyEditor如果被恰當地命名並且放在與它提供支持的class同一個包內,那麼Java標準的JavaBeans PropertyEditor查找機制就會自動地查找到這個PropertyEditor。 如果需要註冊其他的定製的PropertyEditor,這裏也有幾個可用的機制。 最手動的方法,也是通常不方便和不推薦的,就是簡單地使用ConfigurableBeanFactory接口的registerCustomEditor()方法,這是在假定你有一個BeanFactory引用的基礎上。 方便一些的機制是,使用一個特殊的叫做CustomEditorConfigurer的bean factory post-precessor。儘管bean factory post-processor能夠和BeanFactory半手動地使用,但是這一個有一個嵌套的屬性設置。所以強烈建議(如這裏描述的),它能夠和ApplicationContxt一起使用,這樣它就可以以其它bean的方式被部署,並且被自動監測和應用。 3.10.ApplicationContext的簡介 beans包爲管理和操控bean提供了基本的功能,通常是以編程的方式,而context包增加了ApplicationContext,它以一種更加框架導向的方式增強了BeanFactory的功能。多數用戶可以以一種完全的聲明式方式來使用ApplicationContext,甚至不用去手動創建它。然而卻更換爲依賴於諸如ContextLoader的支持類,ContextLoader能夠自動啓動一個ApplicationContext並把這個過程作爲J2EE web應用的普通啓動進程的一部分。當然,這種情況下還是可以編程式地創建一個ApplicationContext。 Context包的基礎是ApplicationContext接口,位於org.springframework.context包中。它是由BeanFactory衍生而來,提供BeanFactory所有的功能。爲了以一種更框架導向的方式工作,使用分層和分等級的context,context包還提供如下: l MessageSource,提供對i18n信息的訪問 l 對資源的訪問,比如URL和文件 l 事件傳播向實現了ApplicationListener接口的bean l 裝載多個(分級的)context,允許每一個專注於一個特定的層次,比如應用的web層 因爲ApplicationContext包括了BeanFactory所有的功能,所以通常建議先於BeanFactory使用,除了很少的一些限定場合比如在一個Applet中,內存的消耗是決定性的,幾個額外kb都會產生不同的結果。接下來的章節將敘述ApplicationContext在BeanFactory的基本能力上增建的功能。 3.11.ApplicationContext增加的功能 正如上面說明的那樣,ApplicationContext有幾個區別於BeanFactory的特性。讓我們一個一個地觀察它們。 3.11.1.使用MessageSource ApplicationContext接口繼承自MessageSource接口,所以提供了信息化(i18n或者國際化)功能。同NestingMessageSource一起使用,就能夠處理分等級的信息,這些是Spring提供的處理信息的基本接口。讓我們快速地瀏覽一下這裏定義的方法: l String getMessage(String code, Object[] args, String default, Locale loc):從MessageSource取得信息的基本方法。如果對於指定的區域沒有找到信息,則使用默認的信息(參數default)。傳進去的參數args是做爲信息中那些要被替換的值,這個是通過Java標準類庫的MessageFormat實現的。 l String getMessage(String code, Object[] args, Locale loc):本質上和上一個方法是一樣的,但是有一點區別:沒有制定默認值;如果信息找不到,就會拋出一個NoSuchMessageException。 l String getMessage(MessageSourceResolveable resolvable, Locale locale):上面兩個方法使用的所有屬性都是封裝到一個叫做MessageSourceResolvable的類中,你可以通過這個方法直接使用它。 當ApplicationContext被加載的時候,它會自動查找在context中定義的MessageSource bean。這個bean必須叫做messageSource。如果找到了這樣的一個bean,所有對上述方法的調用將會被委託給這個找到的message source。如果沒有找到message source,ApplicationContext將會嘗試查它的parent是否包含這個名字的bean。如果有,它將會把找到的bean作爲MessageSource。如果它最終沒有找到任何的信息源,一個空的StaticMessageSource將會被實例化,爲了能夠接受對上述方法的請求。 Spring目前提供了兩個MessageSource的實現。它們是ResourceBundleMessageSource和StaticMessageSource。兩個都實現了NestingMessageSource以便能夠嵌套信息。StaticMessageSource除了提供編程式的方式向source增加message外,幾乎不會被用到。ResourceBundleMessageSource更加有趣些,我們將會提供它的一個例子: <beans> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>format</value> <value>exceptions</value> <value>windows</value> </list> </property> </bean> </beans> 這段配置假定你在classpath有三個resource bundle,分別叫做format, exceptions和windows。使用JDK的標準做法:通過ResourceBundle處理信息,任何處理信息的請求都會被處理。TODO:舉一個例子 3.11.2.傳播事件 ApplicationContext中的事件處理是通過ApplicationEvent類和ApplicationListener接口來提供的。如果context中部署了一個實現了ApplicationListener接口的bean,每次一個ApplicationEvent發佈到ApplicationContext時,那個bean就會被通知。實質上,這是標準的Observer設計模式。Spring提供了三個標準事件: 表3.4。內建的事件 事件 解 釋 ContextRefreshedEvent 當ApplicationContext已經實例化或刷新後發佈的事件。這裏實例化意味着:所有的bean被裝載,singleton被預實例化,以及ApplicationContext準備好可用 ContextClosedEvent 當使用ApplicationContext的close()方法結束context的時候發佈的事件。這裏結束意味着:singleton被銷燬 RequestHandledEvent 一個與web相關的事件,告訴所有的bean一個HTTP請求已經被響應了(這個事件將會在一個請求結束後被髮布)。注意,這個事件只能應用於使用了Spring DispatcherServlet的wen應用 同樣也可以實現自定義的事件。通過調用ApplicationContext的publishEvent()方法,並且指定一個參數,這個參數是你自定義的event class的一個實例。我們來看一個例子。首先是ApplicationContext:
然後是實際的bean:
當然,這個特定的例子或許可以用更好的方式實現(或許使用AOP特性),但是它還是足夠說明基本的事件機制。 3.11.3.在Spring中使用資源 很多應用程序都需要訪問資源。資源可以包括文件,以及其他如web頁面或NNTP newsfeeds的東西。Spring提供了一個清晰透明的方案,以一種協議無關的方式訪問資源。ApplicationContext接口包含一個方法負責這項工作。(getResource(String)) Resource類定義了幾個方法,這幾個方法被所有的Resource實現所共享: 表3.5。資源功能 方法 解 釋 getInputStream() 在資源上打開一個InputStream,並返回這個InputStream。 exists() 驗證資源時候存在,如果不存在返回false。 isOpen() 如果這個資源不能打開多個流將會返回true。對於除了基於文件的資源之外的一些資源會返回false,這些資源不能同時被多個流讀取。 getDescription() 返回資源的描述,通常是全限定名或者實際的URL。 Spring提供了幾個Resource的實現。它們都需要一個String表示的實際的資源位置。依據這個String,Spring將會自動爲你選擇正確的Resource實現。當向ApplicationContext請求一個資源時,Spring首先檢查你指定的資源位置,尋找任何前綴。取決於ApplicationContext的實現,不同的Resource將會是可用的。Resource最好是使用ResourceEditor來配置,比如XmlBeanFactory。 3.12.ApplicationContext中的用戶行爲 BeanFactory已經提供了許多機制用來控制部署在其中的bean的生命週期(比如標誌接口InitializingBean或DisposableBean,它們的結構和XmlBeanFactory配置中的init-method和destroy-method屬性以及bean post-processor是相同的。在ApplicationContext中,這些也同樣可以工作,但同時也增加了一些用於定製beans和容器行爲的機制。 3.12.1.ApplicationContextAware標誌接口 所有BeanFactory中可用的標誌接口這裏也可以使用。ApplicationContext又增加了一個bean可以實現的標誌接口:org.springframework.context.ApplicationContextAware。如果一個bean實現了這個接口並且被部署到了context中,當這個bean創建的時候,將使用這個接口的setApplicationContext()方法回調這個bean,爲這個bean提供一個指向當前context的引用,這個引用將被存儲起來以便bean後面與context發生交互。 3.12.2.BeanPostProcessor Bean post-processor(實現了org.springframework.beans.factory.config. BeanPostProcessor的java類)在上面已經敘述過了。這裏還是值得再次提到,post-processor在ApplicationContext中使用要比在普通的BeanFactory中使用方便得多。在一個ApplicationContext中,一個實現了上述接口的部署的bean將會被自動查找並且作爲一個bean post-processor被註冊,在factory中的每一個bean的創建時都會被適當地調用。 3.12.3.BeanFactoryPostProcessor Bean factory post-processor(實現了org.springframework.beans.factory.config. BeanFactoryPostProcessor接口的java類)在前面已經介紹過。這裏仍然值得再次提一下,bean factory post-processor在ApplicationContext中使用要比在普通的BeanFactory中使用方便得多。在一個ApplicationContext中,一個實現了上述接口的部署的bean將會被自動查找並且作爲一個bean factory post-processor被註冊,在適當的時候被調用。 3.12.4.PropertyPlaceholderConfigurer PropertyPlaceholderConfigurer在BeanFactory中的使用已經敘述過了。這裏仍然值得再次提一下,它在ApplicationContext中的使用要更加方便一些,因爲當它像其它bean一樣部署的時候context會自動識別和應用任何的bean factory post-processor(當然也包括這一個特殊的)。這時候就沒有必要手動地運行它。
3.13.註冊額外的定製的PropertyEditor 正如前面曾提到的那樣,Spring使用標準的JavaBeans PropertyEditor將字符串形式表示的屬性值轉換爲屬性真實的複雜的類型。CustomEditorConfigurer(一個特殊的bean factory post-processor)可以使ApplicationContext方便地增加對額外的PropertyEditor的支持。考慮一個用戶類ExoticType,以及另外一個需要ExoticType作爲屬性的DependsOnExotic的類:
當創建的時候,我們希望能夠以字符串的形式指定屬性,同時幕後的一個PropertyEditor會將這個字符串轉換爲一個真正的Exotic類型的對象:
而這個PorpertyEditor看起來象下面這樣: // converts string representation to ExoticType object
最後,我們用CustomEditorConfigurer將新的PropertyEditor註冊到ApplicationContext上,然後就可以在需要的時候使用這個PropertyEditor了:
3.14.用方法調用的返回值來設置bean的屬性 有些時候,用容器中其它bean的方法返回值來設置一個bean的屬性,或者使用其它任意類(不一定是容器中的bean)的靜態方法的返回值來設置都是很有必要的。此外,有些時候,調用一個靜態或非靜態的方法來執行某些初始化工作也是很有必要的。對於這兩個目的,有個叫作MethodInvokingFactoryBean的助手類可以使用。它是一個FactoryBean,可以返回一個靜態或非靜態方法的調用結果。 下面是一個bean定義的例子,這個bean定義使用那個助手類調用靜態工廠方法:
下面這個例子先調用一個靜態方法,然後調用一個實例方法,來獲得一個Java System的屬性。雖然有點羅嗦,但是可以工作:
注意,實際上這個類多用來訪問工廠方法,所以MethodInvokingFactoryBean默認地以singleton方式進行操作。經由容器的第一次讓factory生產對象的請求將會導致相應的那個工廠方法的調用,這個方法的返回值將會被緩存並且返回給當次請求和後來的請求。這個factory內部的singleton屬性可以被設置爲false,從而導致每次對象的請求都會調用那個目標方法。 通過給targetMethod屬性設置一個靜態方法名字的字符串來指定靜態目標方法,而targetClass則用來指定定義靜態方法的類。同樣地,也可以指定一個目標實例方法,通過targetObject屬性設置目標對象,targetMethod屬性設置要在目標對象上調用的方法的名字。方法調用的參數可以通過設置args屬性來指定。 3.15.在一個web應用中創建一個ApplicationContext 與BeanFactory總是被編程式地創建相比,ApplicationContext可以通過使用諸如ContextLoader而聲明式地被創建。當然你也可以用ApplicationContext的任一種實現來編程式地創建它。首先,我們來看看ContextLoader以及它的實現。 ContextLoader有兩個實現:ContextLoaderListener和ContextLoaderServlet。它們兩個有着同樣的功能,區別是listener不能在兼容Servlet2.2的容器中使用。自從Servelt2.4規範起,listener被要求在web應用啓動後初始化。很多兼容2.3的容器已經實現了這個特性。使用哪一個取決於你自己,但是如果所有的條件都一樣,你大概會更喜歡ContextLoaderListener;關於兼容方面的更多信息可以參照ContextLoaderServlet的JavaDoc。 你可以象下面這樣用ContextLoaderListener註冊一個ApplicationContext:
這個listener需要檢查contextConfigLocation參數。如果不存在的話,它將默認使用/WEB-INF/applicationContext.xml。如果它存在,它就會將那個String用預先定義的分隔符(逗號,分號和空格)分開並將這些值作爲application context將要搜索的位置。ContextLoaderServlet可以替換掉ContextLoaderListener。這個servlet將會像listener那樣使用contextConfigLocation參數。 3.16.粘合的代碼和罪惡的singleton 一個應用中的大多數代碼最好寫成依賴注射(反向控制)的風格,這些代碼將由BeanFactory容器或者ApplicationContext容器來使用,這些代碼將會在被創建的時候從容器處得到它自己的依賴,並且它們是完全不知道容器的存在。然而,對於少量的粘合層的代碼來說有時候就有必要與其它代碼粘合在一起,有時候就需要以一種singleton(或者類似singleton)的方式來訪問BeanFactory或ApplicationContext。舉例來說,第三方的代碼可能會嘗試直接構造一個新的對象(以Class.forName()形式的),卻沒有能力使它從BeanFactory中得到這些對象。如果第三方代碼構造的對象只是一個小的stub或proxy,並且使用singleton方式訪問BeanFactroy/ApplicationContext來獲得真正的對象,這樣反向控制依然對大多數的代碼(由BeanFactory產生的對象)起了作用。因此大多數的代碼依然不知道容器的存在或者容器是如何被訪問的,依然和其它代碼是解耦的,有着所有該有的益處。EJB可以使用這種stub/proxy方案代理到一個普通的java實現的對象,這個對象由BeanFactory產生。雖然理想地BeanFactory不需要是一個singleton,但是如果每個bean使用它自己的non-singleton的BeanFactory,由於內存使用或初始化時間這將變得不切實際。 另一個例子,在一個多層的複雜的J2EE應用中(比如有很多JAR,EJB,以及WAR打包成一個EAR),每一層都有自己的ApplicationContext定義(有效地組成一個層次結構),如果頂層只有一個web-app(WAR)的話,比較好的做法是創建一個由不同層的XML定義文件組成的組合ApplicationContext。所有的ApplicationContext變體都可以從多個定義文件以這種方式構造出來。但是,如果在頂層有多個兄弟web-apps,爲每一個web-app創建一個ApplicationContext是有問題的,每個ApplicationContext都包含大部分相同的底層的bean定義,它會因爲內存使用產生問題,因爲創建多個bean的copy而花很長時間(比如Hibernate SessionFactory),以及其它可能產生的副作用。作爲替換,諸如ContextSingletonBeanFactoryLocator和SingletonBeanFactoryLocator的類可以被用來在需要的時候以有效的singleton方式加載多個分級的(比如一個是另一個的parent)BeanFactory或ApplicationContext,這些將會作爲web-app ApplicationContext的parents。這樣做的結果就是,底層的bean定義只在需要的時候加載並且只被加載一次。 3.16.1.使用SingletonBeanFactoryLocator和ContextSingletonBeanFactoryLocator 你可以查看它們各自的JavaDoc來獲得詳細的使用例子。正如在EJB那一章提到的,Spring爲EJB提供的方便的基類一般使用一個non-singleton的BeanFactoryLocator實現,這個可以在需要的時候被上面兩個類的使用所替換。 | ||||||||||||||||||||||||||||||||||||||||
參與論壇討論:http://www.matrix.org.cn/forum.asp 更多技術文章:http://www.matrix.org.cn/article.asp Matrix java門戶:http://www.matrix.org.cn | ||||||||||||||||||||||||||||||||||||||||
原文地址:http://www.matrix.org.cn/article/1052.html |
spring reference 第三章
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.