《Spring3實戰》摘要(2)--裝配Bean

創建應用對象之間協作關係的行爲通常被稱爲裝配(wiring),這也是依賴注入的本質。本章將介紹使用Spring裝配Bean的基礎知識。因爲依賴注入是Spring的最基本要素,所以在開發基於Spring的應用時,你無時無刻不在使用這些技術。

第二章 裝配Bean

2.1 聲明Bean

Spring 3.0提供兩種配置Bean的方式。傳統上,Spring使用一個或多個XML文件作爲配置文件,而Spring 3.0還同時提供了基於Java註解的配置方式。在3.4節中我們將講解基於Java註解的配置方式。

2.1.1 基於XML文件配置Bean

(1) Spring的核心框架自帶了10個命名空間配置

在XML文件中聲明Bean時,Spring配置文件的根元素來源於Spring beans命名空間所定義的<beans>元素。在<beans>元素內,你可以放置所有的Spring配置信息,包括<bean>元素的聲明。但是beans命名空間並不是唯一的Spring命名空間。

這裏寫圖片描述

(2) 在XML文件中配置Bean舉例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/jee 
        http://www.springframework.org/schema/jee/spring-jee-3.1.xsd" default-lazy-init="true">
    <!-- 這裏創建了一個由Spring容器管理的名字爲duke的Bean,id屬性定義了Bean的名字,也作爲該Bean在Spring容器中的引用。根據class屬性得知,duke是一個Juggler -->
    <bean id="duke" class="com.springinaction.springidol.Juggler" />
</beans>

(3) 使用構造器注入舉例:

當Spring容器加載Bean時,Spring將使用默認的構造器來實例化Bean,如上例,duke會使用new com.springinaction.springidol.Juggler()來創建。

<!-- 通過有int型和bean類型參數的構造器注入Bean,當Spring容器加載Bean時,Spring將使用擁有該類型參數的構造器來實例化Bean, -->
<bean id="duke" class="com.xxx.xxx.Duke">
    <constructor-arg value="15" />
    <constructor-arg ref="其他beanId引用" />
</bean>
**注:**如果想聲明的Bean沒有一個公開的構造方法,可以裝配通過工廠的方法創建的Bean

(4) 通過工廠方法創建Bean

(1) 有時候靜態工廠方法是實例化對象的唯一方法。Spring支持通過<Bean> 元素的factory-method屬性來裝配工廠創建的Bean。

(2) 如果要裝配的對象需要通過靜態方法來創建(例如單例模式),那麼使用factory-method可指定靜態方法實例化Bean。一般來說,單例類的實例只能通過靜態工廠方法來創建。

<!-- 使用factory-method將單例類配置爲Bean,如果要裝配的對象需要通過靜態方法來創建,name這種配置方式可以適用於任何場景 -->
<bean id="theStage" class="com.springinaction.springidols.Stage"
    factory-method="getInstance" />
-----------------------------------------------------------
/**
 *Stage沒有一個公開的構造方法,取而代之的是,靜態方法getInstance()每次被調用時都返回相同的實例(出於線程安全考慮,getInstance()使用了一種被稱爲“initialization on demand holder”的技術來創建單例類的實例)
 */
public class Stage {
    private Stage(){

    }

    private static class StageSingletonHolder{  //延遲加載實例
        static Stage instance = new Stage();
    }

    public static Stage getInstance(){
        return StageSingletonHolder.instance;   //返回實例
    }
}

2.1.2 Bean的作用域

(1) 所有的Spring Bean默認都是單例。當容器分配一個Bean時(不論是通過裝配還是調用容器的getBean()方法),它總是返回Bean的同一個實例。
(2) 當在Spring中配置<bean>元素時,我們可以爲Bean聲明一個作用域。爲了讓Spring在每次請求時都爲Bean產生一個新的實例,我們只需要配置Bean的scope屬性爲prototype即可。
例如:<bean id="ticket" class="com.xx.Ticket" scope="prototype">

除了prototype,Spring還提供了其他幾個作用域選項:
這裏寫圖片描述

2.1.3 初始化和銷燬Bean

(1)爲Bean定義初始化和銷燬操作,只需要使用init-method和destroy-method參數來配置<bean>元素。init-method屬性指定了在初始化Bean時要調用的方法。類似地,destroy-method屬性指定了Bean從容器移除前要調用的方法。

<bean id="auditorium" class="com.spring.springdemo.bean.springidol.Auditorium" 
    init-method="turnOnLights"
    destroy-method="turnOffLights" />
———————————————————————————————————————————————————————————
public class Auditorium {

    public void turnOnLights(){
        System.out.println("表演大廳的燈打開了!");
    }

    public void turnOffLights(){
        System.out.println("表演大廳的燈關閉了!");
    }
}

(2)默認的init-method和destroy-method
如果在上下文中定義的很多Bean都擁有相同名字的初始化方法和銷燬方法,則可以使用<beans>元素的default-init-method和default-destory-method屬性。
如圖:
這裏寫圖片描述

2.2 注入Bean屬性

2.2.1 Spring構造器注入

Spring構造器注入見 2.1.1的第(3)點

2.2.2 Spring設值注入

在Spring中我們可以使用<property>元素配置Bean的屬性。<property>在許多方面都與<constructor-arg>類似,只不過一個是通過構造函數來注入值,另一個是通過調用屬性的setter方法來注入值。

<!-- 舉例Spring的setter設值依賴注入方式  -->
<bean id="saxophone" class="com.spring.springdemo.bean.springidol.Instrument.Saxophone"></bean>
<bean id="kenny" class="com.spring.springdemo.bean.springidol.Instrumentalist">
    <property name="song" value="Jingle Bells" />
    <property name="instrument" ref="saxophone" />
</bean>
-------------------------------------------------------
public class Instrumentalist implements Performer{

    public Instrumentalist() {
    }
    private String song;
    private Instrument instrument;

    @Override
    public void perform() {
        System.out.println("Playing " + song +" : ");
        instrument.play();
    }
    //注入歌曲
    public void setSong(String song){   
        this.song = song;
    }
    //注入樂器
    public void setInstrument(Instrument instrument) {
        this.instrument = instrument;
    }
}

2.2.3 注入內部Bean

Spring設置的Bean能夠被其他Bean所共享,只要將該Bean設置爲屬性即可,爲了防止其他Bean共享,可將該Bean設置爲內部Bean。

<!-- 舉例Spring的內部Bean -->
<bean id="kenny2" class="com.spring.springdemo.bean.springidol.Instrumentalist">
    <property name="song" value="Jingle Bells" />
    <property name="instrument">
        <bean class="com.spring.springdemo.bean.springidol.Instrument.Saxophone" />
    </property>
</bean>
----------------------------------------------------------
<bean id="duke" class="com.spring.springdemo.bean.springidol.PoeticJuggler">
    <constructor-arg  value="15"></constructor-arg>
    <constructor-arg>
        <bean class="com.spring.springdemo.bean.springidol.Sonnect29"/>
    </constructor-arg>
</bean>

2.2.4 使用Spring的命名空間p裝配屬性

Spring的命名空間p提供了另一種Bean屬性的裝配方式,該方式不需要配置如此多的尖括號。命名空間p與<property>等價,其最主要的優點是更簡潔。
命名空間p的schema URI爲http://www.springframework.org/schema/p。如果你想使用命名空間p,只需要在Spring的XML配置中增加如下一段聲明:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

</bean>

通過次聲明,我們可以使用p:作爲<bean>元素所有屬性的前綴來裝配Bean的屬性。舉例如下:

<!-- 使用Spring的命名空間p裝配屬性 -->
<bean id="saxophone1" class="com.spring.springdemo.bean.springidol.Instrument.Saxophone" />
<bean id="kenny3" class="com.spring.springdemo.bean.springidol.Instrumentalist"
    p:song = "Jingle Bells" p:instrument-ref="saxophone1" />

2.2.5 裝配集合

使用value屬性和ref屬性僅在Bean的屬性值爲單個值的情況下才有用。當Bean的屬性值是複數時,即屬性的類型是集合,Spring提供了4種類型的集合配置元素。

集合元素 用於
<list> 裝配list類型的值,允許重複
<set> 裝配set類型的值,不允許重複
<map> 裝配map類型的值,名稱和值可以是任意類型
<props> 裝配properties類型的值,名稱和值必須都是String型


(1)裝配List、Set、Array

當裝配類型爲數組或者java.util.Collection任意實現的屬性時,<list><set>元素非常有用。其實屬性實際定義的集合類型與選擇<list>或者<set>沒有任何關係。如果屬性爲任意的java.util.Collection類型時,這兩個配置元素在使用時幾乎可以完全轉換。
<map><props>這兩個元素分別對應java.util.Map和java.util.Properties。當我們需要由鍵-值對組成的集合時,這兩種配置元素非常有用。這兩種配置元素的關鍵區別在於,<props>要求鍵和值都必須爲String類型,而<map>允許鍵和值可以是任意類型。

public class OneManBank implements Performer {

    public OneManBank() {
    }

    private Collection<Instrument> instruments;
    public void setInstruments(Collection<Instrument> instruments) {
        this.instruments = instruments;
    }

    @Override
    public void perform() {
        for(Instrument instrument : instruments){
            instrument.play();
        }
    }
}
-----------------------------------------------------------
<!-- 舉例Spring裝配集合類型屬性 -->
<!-- list元素包含一個或多個值。這裏的ref元素用來定義Spring上下文中的其他Bean引用,配置了Hank可以演奏吉他、鈸和口琴。當然還可以使用其他的Spring設值元素作爲list的成員,包括<value>、<bean>、和<null/>。實際上,<list>可以包含另外一個<list>z作爲其成員,形成多維列表。
    使用List<Instrument> instruments或Instrument[] instruments配置instruments屬性<list>元素也一樣有效。
 -->
<bean id="hank" class="com.spring.springdemo.bean.springidol.OneManBank">
    <property name="instruments">
        <list>
            <ref bean="guitar" />
            <ref bean="cymbal" />
            <ref bean="harmonica" />
        </list>
    </property>
</bean>
---------------------------------------------------------
<!--同樣,也可以使用<set>元素來裝配集合類型或者數組類型的屬性。
    再次說明,無論<list>還是<set>都可以用來裝配類型爲java.util.Collection的任意實現或者數組的屬性。不能因爲屬性爲java.util.Set類型,就表示用戶必須使用<set>元素來完成轉配。使用<set>元素配置java.util.List類型的屬性是可以的,但需要確保List中的每一個成員都必須是唯一的。
-->
<bean id="hank" class="com.spring.springdemo.bean.springidol.OneManBank">
    <property name="instruments">
        <set>
            <ref bean="guitar" />
            <ref bean="cymbal" />
            <ref bean="harmonica" />
        </set>
    </property>
</bean>


(2)裝配Map集合

<map>中的<entry>元素由一個鍵和一個值組成,鍵和值可以是簡單類型,也可以是其他Bean的引用。這些屬性將幫助我們指定<entry>的鍵和值。當鍵和值都不是String類型時,將鍵-值對注入到Bean屬性的唯一方法就是使用<map>元素。

這裏寫圖片描述

public class OneManBank1 implements Performer {
    public OneManBank1() {
    }
    private Map<String,Instrument> instruments;
    public void setInstruments(Map<String, Instrument> instruments) {
        this.instruments = instruments;
    }

    public void perform() {
        for(String key : instruments.keySet()){
            System.out.println(key + " : ");
            Instrument instrument = instruments.get(key);
            instrument.play();
        }
    }
}
----------------------------------------------------------
<!-- <map>元素聲明瞭一個java.util.Map類型的值。每個<entry>元素定義Map的一個成員。Key屬性指定了entry的鍵,而value-ref屬性定義了entry的值,病引用了Spring上下文中的其他Bean。
-->
<bean id="hank1" class="com.spring.springdemo.bean.springidol.OneManBank.OneManBank1">
    <property name="instruments">
        <map>
            <entry key="GUITAR" value-ref="guitar" />
            <entry key="CYMBAL" value-ref="cymbal" />
            <entry key="HARMONICA" value-ref="harmonicas" />
        </map>
    </property>
</bean>


(3)裝配Properties集合

如果所配置的Map的每一個entry的鍵和值都爲String類型時,我們可以考慮使用java.util.Properties代替Map。Properties類提供了和Map大致相同的功能,但是它限定鍵和值必須爲String類型。

public class OneManBank2 implements Performer {
    public OneManBank2() {
    }

    private Properties instruments;
    public void setInstruments(Properties instruments) {
        this.instruments = instruments;
    }
    @Override
    public void perform() {
        Enumeration enum1 = instruments.propertyNames();//得到配置文件的名字
        while(enum1.hasMoreElements()){
            String strKey = (String) enum1.nextElement();
            String strValue = instruments.getProperty(strKey);
            System.out.println(strKey + "=" + strValue);
        }
    }
}
-----------------------------------------------------------
<!-- <props>元素構建了一個java.util.Properties值,這個Properties的每個成員都由<prop>元素定義。每個<prop>元素都有一個key屬性,其定義了Properties每個成員的鍵,而每一個成員的值由<prop>元素的內容所定義。 -->
<bean id="hank2" class="com.spring.springdemo.bean.springidol.OneManBank.OneManBank2">
    <property name="instruments">
        <props>
            <prop key="GUITAR">STRUM STRUM STRUM</prop>
            <prop key="CYMBAL">CRASH CRASH CRASH</prop>
            <prop key="HARMONICA">HUM HUM HUM</prop>
        </props>
    </property>
</bean>

(4)裝配空值

<property name="someNonNullProperty"><null/></property>

2.3 使用表達式裝配(SpEL)

Spring 3 引入了Spring表達式語言(Spring Expression Language,SpEL)。SpEL是一種強大、簡潔的裝配Bean的方式,它通過運行期執行的表達式將值裝配到Bean的屬性或構造器參數中。使用SpEL,可以實現超乎想象的裝配效果,這是使用傳統的Spring裝配方式難以做到的(甚至是不可能的)。

SpEL擁有許多特性,包括:

  • 使用Bean的ID來引用Bean;
  • 調用方法和訪問對象的屬性;
  • 對值進行算術、關係和邏輯運算;
  • 正則表達式匹配;
  • 集合操作。

注: 因爲SpEL表達式最終是一個字符串,不易於測試,也沒有IDE的語法檢查的支持,所以建議在使用傳統方式很難進行裝配,而使用SpEL卻很容易實現的場景下才使用SpEL。要小心,不要把過多的邏輯放入SpEL表達式中。

2.3.1 SpEL的基本原理

SpEL表達式的首要目標是通過計算獲得某個值。在計算這個數值的過程中,會使用到其他的值並會對這些值進行操作。最簡單的SpEL求值或許是對字面值、Bean的屬性或者某個類的常量進行求值。

(1)字面值

<!-- #{}標記會提示Spring這個標記裏的內容是SpEL表達式。 -->
<property name="count" value="#{5}" />
<!-- 與非SpEL表達式混用、浮點型、科學計數法、字符串型、布爾型 -->
<property name="message" value="The value is #{5}" />
<property name="frequency" value="#{89.7}" />
<property name="capacity" value="#{1e4}" />
<property name="name" value="#{'Chuck'}" />
<property name="name" value='{"Chuck"}' />
<property name="enabled" value="#{false}" />

(2)引用Bean、Properties和方法

<!-- 使用SpEL將ID爲saxophone的Bean裝配到instrument屬性中 -->
<property name="instrument" value="#{saxophone}" />

<!-- 配置id爲"carl"的Bean,carl的song屬性值爲id="kenny" 的Bean的song屬性值,需要類中有getSong()方法 -->
<bean id="carl" class="com.spring.springdemo.bean.springidol.Instrumentalist">
    <property name="song" value="#{kenny.song}" />
</bean>

<!-- 調用id="songSelector"的bean中的selectSong()方法,並使返回的字符串大寫 -->
<bean id="carl" class="com.spring.springdemo.bean.springidol.Instrumentalist">
    <property name="song" value="#{songSelector.selectSong().toUpperCase()}" />
</bean>

<!-- 使用null-safe存儲器,防止因songSelector.selectSong()方法返回null時報空指針異常。使用"?."來代替"."來訪問toUpperCase()方法。如果selectSong()返回null值,SpEL就不再嘗試調用toUpperCase()方法 -->
<bean id="carl" class="com.spring.springdemo.bean.springidol.Instrumentalist">
    <property name="song" value="#{songSelector.selectSong()?.toUpperCase()}" />
</bean>

(3)操作類
在SpEL中,使用 T() 運算符會調用類的作用域的方法和常量。例如T(java.lang.Math),會返回一個Math的類對象。該運算符可以訪問指定類的靜態方法和常量

<!-- 將PI的值裝配到Bean的一個屬性中 -->
<property name="multiplier" value="#{T(java.lang.Math).PI}" />

<!-- 將一個0到1之間的隨機數裝配到Bean的一個屬性中 -->
<property name="multiplier" value="#{T(java.lang.Math).random()}" />

2.3.2 在SpEL值上執行操作

SpEL提供了幾種運算符,這些運算符可以用在SpEL表達式中的值上。
這裏寫圖片描述

(1)使用SpEL進行數值運算

<!-- 把id="counter"的Bean的total屬性值加42賦值給adjustedAmount屬性 -->
<property name="adjustedAmount" value="#{counter.total + 42}" />

<!-- "+"號的字符串連接 -->
<property name="fullName" value="#{performer.firstName + ' ' + performer.lastName}" />

(2)比較值

<!-- 比較兩個值是否相等,如果相等,Spring會將true裝配給equal屬性 -->
<property name="equal" value="#{counter.total == 42}" />

在Spring的XML配置文件中使用<=和>=符號時,會報錯,這是因爲這兩個符號在XML中有特殊含義。當在XML中使用SpEL時,最好對這些運算符使用SpEL的文本代替方式(textual alternatives),如下所示:

<!-- 這裏le運算符代表<=號 -->
<property name="hasCapacity" value="#{counter.total le 100000}" />

這裏寫圖片描述

(3)邏輯表達式
這裏寫圖片描述

<!-- 使用and運算符 -->
<property name="largeCircle" value="#{sharp.kind == 'circle' and shape.perimeter gt 10000}" />
<!-- 布爾類型的表達式求反,運算符爲!或not -->
<property name="outOfStock" value="#{!product.available}" />

(4)條件表達式
使用SpEL的三元運算符”?:”

<property name="instrument" value="#{songSelector.selectSong() == 'Jingle Bells' ? piano : saxophone}" />
<!-- 三元運算符的變體,如果kenny.song不爲null,那麼表達式的求值結果是kenny.song,否則就是"Greensleeves"。當我們以這種方式使用時,"?:"通常被稱爲elvis運算符 -->
<property name="song" value="#{kenny.song ?: 'Greensleeves'"} />

(5)SpEL的正則表達式
SpEL通過matches運算符支持表達式中的模式匹配。
matches運算符對String類型的文本(左參數)應用正則表達式(右參數)。matches的運算結果將返回一個布爾類型的值。

<property name="validEmail" value="#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'} />

2.3.3 在SpEL中篩選集合

舉例:

public class City{
    private String name;
    private String state;
    private int population;
    ...
    getter/setter方法
    ...
}   
-------------------------------------------------
<!-- `<util:list>`元素是由Spring的util命名空間所定義的。它創建了一個java.util.List類型的Bean,這個集合中包含了所有參數配置的Bean -->
<util:list id="cities">
    <bean class="com.spring.springdemo.bean.springidol.City"
        p:name="Chicago" p:state="IL" p:population="2853114" />
    <bean class="com.spring.springdemo.bean.springidol.City"
        p:name="Atlanta" p:state="GA" p:population="537958" />
    <bean class="com.spring.springdemo.bean.springidol.City"
        p:name="Dallas" p:state="TX" p:population="1279910" />
    <bean class="com.spring.springdemo.bean.springidol.City"
        p:name="Houston" p:state="TX" p:population="2242193" />
</util:list>

(1)訪問集合成員

中括號”[]”運算符會始終通過索引訪問集合中的成員。
例如:#{‘This is a test’[3]},將返回’s’。

<!-- 從集合中提取一個成員,並將它裝配到某個屬性中 -->
<property name="chosenCity" value="#{cities[2]}" />
<!-- 隨機選擇一個city -->
<property name="chosenCity" value="#{cities[T(java.lang.Math).random()*cities.size()]}" />

<!-- "[]"運算符同樣可以用來獲取java.util.Map集合中的成員。假設City對象以其名字作爲鍵放入Map集合中。在這種場景下,我們可以像下面所展示的那樣獲取鍵爲Dallas的entry -->
<!-- 從集合中提取一個成員,並將它裝配到某個屬性中 -->
<property name="chosenCity" value="#{cities['Chicago']}" />

“[]”運算符的另一種用法是從java.util.Properties集合中獲取值。

<!-- 假設我們需要通過`<util:properties>`元素在Spring中加載一個properties配置文件。-->
<util:properties id="settings" 
     location="classpath:settings.properties" />
<!--settings Bean是一個java.util.Properties類,加載了名爲settings.properties的文件。使用SpEL從settings Bean中訪問一個名爲twitter.accessToken的屬性。
 -->
 <property name="accessToken" value="#{settings['twitter.accessToken']}" />

Spring還爲SpEL創造了兩種特殊的選擇屬性的方式:systemEnvironment 和 systemProperties。

  • systemEnvironment 包含了應用程序所在機器上的所有環境變量。它恰巧是一個java.util.Properties集合,所以我們可以使用”[]”運算符通過鍵來訪問Properties集合中的成員。
<!-- 在MacOS X 電腦上,將用戶home目錄的路徑注入到一個bean的屬性中 -->
<property name="homePath" value="#{systemEnvironment['HOME']}" />
  • systemProperties 包含了Java應用程序啓動時所設置的所有屬性(通常通用 -D 參數)。
<!-- 如果使用 -Dapplication.home=/etc/myapp,來啓動JVM,那麼你就可以通過以下SpEL表達式將該值注入homePath屬性中。 -->
<property name="homePath" value="#{systemProperties['application.home']}" />

(2)查詢集合成員
可以使用查詢運算符 “?.[]” 可以簡單的查詢滿足某項條件的成員。查詢運算符會創建一個新的集合,新的集合中只存放符合中括號內表達式的成員。

<!-- 查詢人口多於10000的城市 -->
<property name="bigCities" value="#{cities.?[population gt 10000]}"

SpEL同樣提供了兩種其他查詢運算符:”.^[]” 和 “.$[]” ,從集合中查詢出第一個匹配項和最後一個匹配項。

<!-- 查詢第一個符合條件人口多於10000的城市 -->
<property name="bigCities" value="#{cities.^[population gt 10000]}"

(3)投影集合
集合投影是從集合的每一個成員中選擇特定的屬性放入一個新的集合中。SpEL的投影運算符 “.![]”完全可以做到這點。

<!-- 將cities中的城市名抽取出來形成一個新的集合賦值給cityNames屬性 -->
<property name="cityNames" value="#{cities.![name]}"

<!-- 將cities中的城市名和國家名以"name,state"的格式抽取出來形成一個新的集合賦值給cityNames屬性 -->
<property name="cityNames" value="#{cities.![name+','+state]}"

<!-- 將cities中符合條件城市的城市名和國家名以"name,state"的格式抽取出來形成一個新的集合賦值給cityNames屬性 -->
<property name="cityNames" value="#{cities.?[population gt 10000].![name+','+state]}"
發佈了66 篇原創文章 · 獲贊 37 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章