閱讀299 返回首頁    go 阿裏雲 go 技術社區[雲棲]


《Spring實戰(第4版)》——2.4 通過XML裝配bean

本節書摘來自異步社區《Spring實戰(第4版)》一書中的第2章,第2.4節,作者: 【美】Craig Walls(沃爾斯)著,更多章節內容可以訪問雲棲社區“異步社區”公眾號查看

2.4 通過XML裝配bean

到此為止,我們已經看到了如何讓Spring自動發現和裝配bean,還看到了如何進行手動幹預,即通過JavaConfig顯式地裝配bean。但是,在裝配bean的時候,還有一種可選方案,盡管這種方案可能不太合乎大家的心意,但是它在Spring中已經有很長的曆史了。

在Spring剛剛出現的時候,XML是描述配置的主要方式。在Spring的名義下,我們創建了無數行XML代碼。在一定程度上,Spring成為了XML配置的同義詞。

盡管Spring長期以來確實與XML有著關聯,但現在需要明確的是,XML不再是配置Spring的唯一可選方案。Spring現在有了強大的自動化配置和基於Java的配置,XML不應該再是你的第一選擇了。

不過,鑒於已經存在那麼多基於XML的Spring配置,所以理解如何在Spring中使用XML還是很重要的。但是,我希望本節的內容隻是用來幫助你維護已有的XML配置,在完成新的Spring工作時,希望你會使用自動化配置和JavaConfig。

2.4.1 創建XML配置規範
在使用XML為Spring裝配bean之前,你需要創建一個新的配置規範。在使用JavaConfig的時候,這意味著要創建一個帶有@Configuration注解的類,而在XML配置中,這意味著要創建一個XML文件,並且要以元素為根。

最為簡單的Spring XML配置如下所示:

screenshot

很容易就能看出來,這個基本的XML配置已經比同等功能的JavaConfig類複雜得多了。作為起步,在JavaConfig中所需要的隻是@Configuration,但在使用XML時,需要在配置文件的頂部聲明多個XML模式(XSD)文件,這些文件定義了配置Spring的XML元素。

借助Spring Tool Suite創建XML配置文件創建和管理Spring XML配置文件的一種簡便方式是使用Spring Tool Suite。在Spring Tool Suite的菜單中,選擇File>New>Spring Bean Configuration File,能夠創建Spring XML配置文件,並且可以選擇可用的配置命名空間。
用來裝配bean的最基本的XML元素包含在spring-beans模式之中,在上麵這個XML文件中,它被定義為根命名空間。是該模式中的一個元素,它是所有Spring配置文件的根元素。

在XML中配置Spring時,還有一些其他的模式。盡管在本書中,我更加關注自動化以及基於Java的配置,但是在本書講解的過程中,當出現其他模式的時候,我至少會提醒你。

就這樣,我們已經有了一個合法的Spring XML配置。不過,它也是一個沒有任何用處的配置,因為它(還)沒有聲明任何bean。為了給予它生命力,讓我們重新創建一下CD樣例,隻不過我們這次使用XML配置,而不是使用JavaConfig和自動化裝配。

2.4.2 聲明一個簡單的
要在基於XML的Spring配置中聲明一個bean,我們要使用spring-beans模式中的另外一個元素:。元素類似於JavaConfig中的@Bean注解。我們可以按照如下的方式聲明CompactDiscbean:

screenshot

這裏聲明了一個很簡單的bean,創建這個bean的類通過class屬性來指定的,並且要使用全限定的類名。

因為沒有明確給定ID,所以這個bean將會根據全限定類名來進行命名。在本例中,bean的ID將會是“soundsystem.SgtPeppers#0”。其中,“#0”是一個計數的形式,用來區分相同類型的其他bean。如果你聲明了另外一個SgtPeppers,並且沒有明確進行標識,那麼它自動得到的ID將會是“soundsystem.SgtPeppers#1”。

盡管自動化的bean命名方式非常方便,但如果你要稍後引用它的話,那自動產生的名字就沒有多大的用處了。因此,通常來講更好的辦法是借助id屬性,為每個bean設置一個你自己選擇的名字:

screenshot

稍後將這個bean裝配到CDPlayer bean之中的時候,你會用到這個具體的名字。

減少繁瑣為了減少XML中繁瑣的配置,隻對那些需要按名字引用的bean(比如,你需要將對它的引用注入到另外一個bean中)進行明確地命名。
在進一步學習之前,讓我們花點時間看一下這個簡單bean聲明的一些特征。

第一件需要注意的事情就是你不再需要直接負責創建SgtPeppers的實例,在基於JavaConfig的配置中,我們是需要這樣做的。當Spring發現這個元素時,它將會調用SgtPeppers的默認構造器來創建bean。在XML配置中,bean的創建顯得更加被動,不過,它並沒有JavaConfig那樣強大,在JavaConfig配置方式中,你可以通過任何可以想象到的方法來創建bean實例。

另外一個需要注意到的事情就是,在這個簡單的聲明中,我們將bean的類型以字符串的形式設置在了class屬性中。誰能保證設置給class屬性的值是真正的類呢?Spring的XML配置並不能從編譯期的類型檢查中受益。即便它所引用的是實際的類型,如果你重命名了類,會發生什麼呢?

借助IDE檢查XML的合法性使用能夠感知Spring功能的IDE,如Spring Tool Suite,能夠在很大程度上幫助你確保Spring XML配置的合法性。
以上介紹的隻是JavaConfig要優於XML配置的部分原因。我建議在為你的應用選擇配置風格時,要記住XML配置的這些缺點。接下來,我們繼續Spring XML配置的學習進程,了解如何將SgtPeppersbean注入到CDPlayer之中。

2.4.3 借助構造器注入初始化bean
在Spring XML配置中,隻有一種聲明bean的方式:使用元素並指定class屬性。Spring會從這裏獲取必要的信息來創建bean。

但是,在XML中聲明DI時,會有多種可選的配置方案和風格。具體到構造器注入,有兩種基本的配置方案可供選擇:

元素
使用Spring 3.0所引入的c-命名空間
兩者的區別在很大程度就是是否冗長煩瑣。可以看到,元素比使用c-命名空間會更加冗長,從而導致XML更加難以讀懂。另外,有些事情可以做到,但是使用c-命名空間卻無法實現。

在介紹Spring XML的構造器注入時,我們將會分別介紹這兩種可選方案。首先,看一下它們各自如何注入bean引用。

構造器注入bean引用
按照現在的定義,CDPlayerbean有一個接受CompactDisc類型的構造器。這樣,我們就有了一個很好的場景來學習如何注入bean的引用。

現在已經聲明了SgtPeppers bean,並且SgtPeppers類實現了CompactDisc接口,所以實際上我們已經有了一個可以注入到CDPlayerbean中的bean。我們所需要做的就是在XML中聲明CDPlayer並通過ID引用SgtPeppers:

screenshot

當Spring遇到這個元素時,它會創建一個CDPlayer實例。元素會告知Spring要將一個ID為compactDisc的bean引用傳遞到CDPlayer的構造器中。

作為替代的方案,你也可以使用Spring的c-命名空間。c-命名空間是在Spring 3.0中引入的,它是在XML中更為簡潔地描述構造器參數的方式。要使用它的話,必須要在XML的頂部聲明其模式,如下所示:

screenshot

在c-命名空間和模式聲明之後,我們就可以使用它來聲明構造器參數了,如下所示:

screenshot

在這裏,我們使用了c-命名空間來聲明構造器參數,它作為元素的一個屬性,不過這個屬性的名字有點詭異。圖2.1描述了這個屬性名是如何組合而成的。

screenshot

圖2.1 通過Spring的c-命名空間將bean引用注入到構造器參數中

屬性名以“c:”開頭,也就是命名空間的前綴。接下來就是要裝配的構造器參數名,在此之後是“-ref”,這是一個命名的約定,它會告訴Spring,正在裝配的是一個bean的引用,這個bean的名字是compactDisc,而不是字麵量“compactDisc”。

很顯然,使用c-命名空間屬性要比使用元素簡練得多。這是我很喜歡它的原因之一。除了更易讀之外,當我在編寫樣例代碼時,c-命名空間屬性能夠更加有助於使代碼的長度保持在書的邊框之內。

在編寫前麵的樣例時,關於c-命名空間,有一件讓我感到困擾的事情就是它直接引用了構造器參數的名稱。引用參數的名稱看起來有些怪異,因為這需要在編譯代碼的時候,將調試標誌(debug symbol)保存在類代碼中。如果你優化構建過程,將調試標誌移除掉,那麼這種方式可能就無法正常執行了。

替代的方案是我們使用參數在整個參數列表中的位置信息:

screenshot

這個c-命名空間屬性看起來似乎比上一種方法更加怪異。我將參數的名稱替換成了“0”,也就是參數的索引。因為在XML中不允許數字作為屬性的第一個字符,因此必須要添加一個下畫線作為前綴。

使用索引來識別構造器參數感覺比使用名字更好一些。即便在構建的時候移除掉了調試標誌,參數卻會依然保持相同的順序。如果有多個構造器參數的話,這當然是很有用處的。在這裏因為隻有一個構造器參數,所以我們還有另外一個方案——根本不用去標示參數:

screenshot

到目前為止,這是最為奇特的一個c-命名空間屬性,這裏沒有參數索引或參數名。隻有一個下畫線,然後就是用“-ref”來表明正在裝配的是一個引用。

我們已經將引用裝配到了其他的bean之中,接下來看一下如何將字麵量值(literal value)裝配到構造器之中。

將字麵量注入到構造器中
迄今為止,我們所做的DI通常指的都是類型的裝配——也就是將對象的引用裝配到依賴於它們的其他對象之中——而有時候,我們需要做的隻是用一個字麵量值來配置對象。為了闡述這一點,假設你要創建CompactDisc的一個新實現,如下所示:

screenshot

在SgtPeppers中,唱片名稱和藝術家的名字都是硬編碼的,但是這個CompactDisc實現與之不同,它更加靈活。像現實中的空磁盤一樣,它可以設置成任意你想要的藝術家和唱片名。現在,我們可以將已有的SgtPeppers替換為這個類:

screenshot

我們再次使用元素進行構造器參數的注入。但是這一次我們沒有使用“ref”屬性來引用其他的bean,而是使用了value屬性,通過該屬性表明給定的值要以字麵量的形式注入到構造器之中。

如果要使用c-命名空間的話,這個例子又該是什麼樣子呢?第一種方案是引用構造器參數的名字:

screenshot

可以看到,裝配字麵量與裝配引用的區別在於屬性名中去掉了“-ref”後綴。與之類似,我們也可以通過參數索引裝配相同的字麵量值,如下所示:

screenshot

XML不允許某個元素的多個屬性具有相同的名字。因此,如果有兩個或更多的構造器參數的話,我們不能簡單地使用下畫線進行標示。但是如果隻有一個構造器參數的話,我們就可以這樣做了。為了完整地展現該功能,假設BlankDisc隻有一個構造器參數,這個參數接受唱片的名稱。在這種情況下,我們可以在Spring中這樣聲明它:

screenshot

在裝配bean引用和字麵量值方麵,和c-命名空間的功能是相同的。但是有一種情況是能夠實現,c-命名空間卻無法做到的。接下來,讓我們看一下如何將集合裝配到構造器參數中。

裝配集合
到現在為止,我們假設CompactDisc在定義時隻包含了唱片名稱和藝術家的名字。如果現實世界中的CD也是這樣的話,那麼在技術上就不會任何的進展。CD之所以值得購買是因為它上麵所承載的音樂。大多數的CD都會包含十多個磁道,每個磁道上包含一首歌。

如果使用CompactDisc為真正的CD建模,那麼它也應該有磁道列表的概念。請考慮下麵這個新的BlankDisc:

screenshot

這個變更會對Spring如何配置bean產生影響,在聲明bean的時候,我們必須要提供一個磁道列表。

最簡單的辦法是將列表設置為null。因為它是一個構造器參數,所以必須要聲明它,不過你可以采用如下的方式傳遞null給它:

screenshot

元素所做的事情與你的期望是一樣的:將null傳遞給構造器。這並不是解決問題的好辦法,但在注入期它能正常執行。當調用play()方法時,你會遇到NullPointerException異常,因此這並不是理想的方案。

更好的解決方法是提供一個磁道名稱的列表。要達到這一點,我們可以有多個可選方案。首先,可以使用元素將其聲明為一個列表:

screenshot

其中,元素是的子元素,這表明一個包含值的列表將會傳遞到構造器中。其中,元素用來指定列表中的每個元素。

與之類似,我們也可以使用元素替代,實現bean引用列表的裝配。例如,假設你有一個Discography類,它的構造器如下所示:

screenshot

那麼,你可以采取如下的方式配置Discography bean:

screenshot

當構造器參數的類型是java.util.List時,使用元素是合情合理的。盡管如此,我們也可以按照同樣的方式使用元素:

screenshot

和元素的區別不大,其中最重要的不同在於當Spring創建要裝配的集合時,所創建的是java.util.Set還是java.util.List。如果是Set的話,所有重複的值都會被忽略掉,存放順序也不會得以保證。不過無論在哪種情況下,或都可以用來裝配List、Set甚至數組。

在裝配集合方麵,比c-命名空間的屬性更有優勢。目前,使用c-命名空間的屬性無法實現裝配集合的功能。

使用和c-命名空間實現構造器注入時,它們之間還有一些細微的差別。但是到目前為止,我們所涵蓋的內容已經足夠了,尤其是像我之前所建議的那樣,要首選基於Java的配置而不是XML。因此,與其不厭其煩地花費時間講述如何使用XML進行構造器注入,還不如看一下如何使用XML來裝配屬性。

2.4.4 設置屬性
到目前為止,CDPlayer和BlankDisc類完全是通過構造器注入的,沒有使用屬性的Setter方法。接下來,我們就看一下如何使用Spring XML實現屬性注入。假設屬性注入的CDPlayer如下所示:

screenshot

該選擇構造器注入還是屬性注入呢?作為一個通用的規則,我傾向於對強依賴使用構造器注入,而對可選性的依賴使用屬性注入。按照這個規則,我們可以說對於BlankDisc來講,唱片名稱、藝術家以及磁道列表是強依賴,因此構造器注入是正確的方案。不過,對於CDPlayer來講,它對CompactDisc是強依賴還是可選性依賴可能會有些爭議。雖然我不太認同,但你可能會覺得即便沒有將CompactDisc裝入進去,CDPlayer依然還能具備一些有限的功能。

現在,CDPlayer沒有任何的構造器(除了隱含的默認構造器),它也沒有任何的強依賴。因此,你可以采用如下的方式將其聲明為Spring bean:

screenshot

Spring在創建bean的時候不會有任何的問題,但是CDPlayerTest會因為出現NullPointerException而導致測試失敗,因為我們並沒有注入CDPlayer的compactDisc屬性。不過,按照如下的方式修改XML,就能解決該問題:

screenshot

元素為屬性的Setter方法所提供的功能與元素為構造器所提供的功能是一樣的。在本例中,它引用了ID為compactDisc的bean(通過ref屬性),並將其注入到compactDisc屬性中(通過setCompactDisc()方法)。如果你現在運行測試的話,它應該就能通過了。

我們已經知道,Spring為元素提供了c-命名空間作為替代方案,與之類似,Spring提供了更加簡潔的p-命名空間,作為元素的替代方案。為了啟用p-命名空間,必須要在XML文件中與其他的命名空間一起對其進行聲明:

screenshot

我們可以使用p-命名空間,按照以下的方式裝配compactDisc屬性:

screenshot

p-命名空間中屬性所遵循的命名約定與c-命名空間中的屬性類似。圖2.2闡述了p-命名空間屬性是如何組成的。

screenshot

圖2.2 借助Spring的p-命名空間,將bean引用注入到屬性中

首先,屬性的名字使用了“p:”前綴,表明我們所設置的是一個屬性。接下來就是要注入的屬性名。最後,屬性的名稱以“-ref”結尾,這會提示Spring要進行裝配的是引用,而不是字麵量。

將字麵量注入到屬性中
屬性也可以注入字麵量,這與構造器參數非常類似。作為示例,我們重新看一下BlankDisc bean。不過,BlankDisc這次完全通過屬性注入進行配置,而不是構造器注入。新的BlankDisc類如下所示:

screenshot

現在,它不再強製要求我們裝配任何的屬性。你可以按照如下的方式創建一個BlankDiscbean,它的所有屬性全都是空的:

screenshot

當然,如果在裝配bean的時候不設置這些屬性,那麼在運行期CD播放器將不能正常播放內容。play()方法可能會遇到的輸出內容是“Playing null by null”,隨之會拋出NullPointerException異常,這是因為我們沒有指定任何的磁道。所以,我們需要裝配這些屬性,可以借助元素的value屬性實現該功能:

screenshot

在這裏,除了使用元素的value屬性來設置title和artist,我們還使用了內嵌的元素來設置tracks屬性,這與之前通過裝配tracks是完全一樣的。

另外一種可選方案就是使用p-命名空間的屬性來完成該功能:

screenshot

與c-命名空間一樣,裝配bean引用與裝配字麵量的唯一區別在於是否帶有“-ref”後綴。如果沒有“-ref”後綴的話,所裝配的就是字麵量。

但需要注意的是,我們不能使用p-命名空間來裝配集合,沒有便利的方式使用p-命名空間來指定一個值(或bean引用)的列表。但是,我們可以使用Spring util-命名空間中的一些功能來簡化BlankDiscbean。

首先,需要在XML中聲明util-命名空間及其模式:

screenshot

util-命名空間所提供的功能之一就是util:list元素,它會創建一個列表的bean。借助util:list,我們可以將磁道列表轉移到BlankDisc bean之外,並將其聲明到單獨的bean之中,如下所示:

screenshot

現在,我們能夠像使用其他的bean那樣,將磁道列表bean注入到BlankDisc bean的tracks屬性中:

screenshot

util:list隻是util-命名空間中的多個元素之一。表2.1列出了util-命名空間提供的所有元素。

在需要的時候,你可能會用到util-命名空間中的部分成員。但現在,在結束本章前,我們看一下如何將自動化配置、JavaConfig以及XML配置混合並匹配在一起。
screenshot

最後更新:2017-05-31 11:04:39

  上一篇:go  LXD 2.0 係列(四):資源控製
  下一篇:go  如何用Word2vec輕鬆處理新金融風控場景中的文本類數據