《Spring實戰》學習筆記-第二章:裝配Bean

創建應用對象之間協作關系的行為通常被稱作裝配(Wiring),這也是依賴注入的本質。

聲明Bean

創建Spring配置

Spring容器提供了兩種配置Bean的方式,其一是使用XML文件作為配置文件,其二是基于Java注解的配置方式。
以下是一個典型的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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 此處聲明各個Bean -->

</beans>

在<beans>標簽內可以放置相關的Spring配置信息,另外,Spring的核心框架自帶了10個命名空間的配置:

命名空間 用途
aop 為聲明切面以及將@AspectJ注解的類代理為Spring切面提供了配置元素
beans beans支持聲明Bean和裝配Bean,是Spring最核心也是最原始的命名空間
context 為配置Spring應用上下文提供了配置元素,包括自動檢測和自動裝配Bean、注入非Spring直接管理的對象
jee 提供了與Java EE API 的集成,例如JNDI和EJB
jms 為聲明消息驅動的POJO提供了配置元素
lang 支持配置由Groovy、JRuby或BeanShell等腳本實現的Bean
mvc 啟用Spring MVC的能力,例如面向注解的控制器、視圖控制器和攔截器
oxm 支持Spring 的對象到XML映射配置
tx 提供聲明式事務配置
util 提供各種各樣的工具類元素,包括把集合配置為Bean、支持屬性占位符元素

聲明一個簡單的Bean

package com.springinaction.springidol;

public class Juggler implements Performer {

    private int beanBags = 3;

    public Juggler() {
    }

    public Juggler(int beanBags) {
        this.beanBags = beanBags;
    }

    @Override
    public void perform() throws PerformanceException {
        System.out.println("JUGGLING " + beanBags + " BEANBAGS");
    }

}
<bean id="duke" class="com.springinaction.springidol.Juggler"></bean>

<bean>元素是Spring中最基本的配置單元,通過該元素Spring將創建一個對象。當Spring容器加載該Bean時,Spring將使用默認的構造器來實例化該Bean,實際上,duke會使用如下代碼來創建:
new com.springinaction.springidol.Juggler();

通過構造器注入

讓bean使用另外一個構造方法:

<bean id="duke" class="com.springinaction.springidol.Juggler">
    <constructor-arg value="15"></constructor-arg>
</bean>

在構造Bean的時候,可以使用<constructor-arg>標簽來告訴Spring額外的信息,這樣Spring就不再會使用默認的構造器來實例化該Bean。
測試代碼:

package com.springinaction.springidol;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring {

    @Test
    public void testDuke() throws PerformanceException {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-idol.xml");
        Performer performer = (Performer) context.getBean("duke");
        performer.perform();
    }
}

通過運行結果JUGGLING 15 BEANBAGS可以15被注入到了構造器中。

通過構造器注入對象引用

現在需要一個新的類PoeticJuggler,該類需要Poem類作為其參數并通過構造器注入。

package com.springinaction.springidol;

public class PoeticJuggler extends Juggler {
    private Poem poem;

    // 注入Poem
    public PoeticJuggler(Poem poem) {
        super();
        this.poem = poem;
    }

    // 注入豆袋子數量和Poem
    public PoeticJuggler(int beanBags, Poem poem) {
        super(beanBags);
        this.poem = poem;
    }

    @Override
    public void perform() throws PerformanceException {
        super.perform();
        System.out.println("While reciting...");
        poem.recite();
    }
}

在配置文件中就需要聲明一個 Poem的類,并將其注入到PoeticJuggler中:

<bean id="sonnet29" class="com.springinaction.springidol.Sonnet29"></bean>

<bean id="poeticDuke" class="com.springinaction.springidol.PoeticJuggler">
    <constructor-arg value="15" />
    <constructor-arg ref="sonnet29" />
</bean>

這里使用ref屬性來將Id為sonnet29的Bean引用傳遞給構造器,當Spring遇到sonnet29和poeticDuke的bean聲明時,所執行的邏輯腳本將是:

Poem sonnet29 = new Sonnet29();
Performer poeticDuke = new PoeticJuggler(15, sonnet29 );

通過工廠方法創建Bean

在沒有公開的構造方法時,可以通過工廠方法來創建Bean,及<bean>元素的factory-method屬性來裝配工廠創建的Bean。
比如Stage是一個沒有公開構造方法的類,但是可以通過getInstance獲取其實例,那么可以通過下面的配置方式:

<bean id="theStage" class="com.springinaction.springidol.Stage"
    factory-method="getInstance" />

Bean的作用域

作用域 定義
singleton(默認) 在每一個Spring容器中,一個Bean定義只有一個對象實例
prototype 允許Bean的定義可以被實例化任意次(每次調用都創建一個實例)
request 在一次HTTP請求中,每個Bean定義對應一個實例。該作用域僅在基于Web的Spring上下文(例如SpringMVC)中才有效
session 在一個HTTP Sesion中,每個Bean定義對應一個實例。該作用域僅在基于Web的Spring上下文(例如SpringMVC)中才有效
global-session 在一個全局HTTP Sesion中,每個Bean定義對應一個實例。該作用域僅在Portlet上下文中才有效

配置方法,設置<bean>標簽的scope屬性:

<bean id="sonnet29" class="com.springinaction.springidol.Sonnet29" scope="prototype"/>

Spring的單例只能保證在每個應用上下文中只有一個Bean的實例,你也可以通過定義多個<bean>的方式來實例化同一個Bean。

初始化和銷毀Bean

可以為Bean定義初始化和銷毀操作,只需使用init-methoddestroy-method參數來配置<bean>標簽即可。

比如,舞臺(Auditorium)需要在表演開始前開燈(turnOnLights),在結束時關燈(turnOffLights),那么就可以做下面的聲明:

<bean id="auditorium" class="com.springinaction.springidol.Auditorium"
    init-method="turnOnLights" destroy-method="turnOffLights" />

默認的init-method和destroy-method

可以使用<beans>的default-init-methoddefault-destroy-method為上下文中所有的Bean設置共同的初始化和銷毀方法。

注入Bean屬性

Spring可以借助屬性的set方法來配置屬性的值,以實現setter方式的注入。

下面是一個音樂家(Instrumentalist)類,它演奏時需要歌曲(song)和樂器(instrument)兩個屬性。

package com.springinaction.springidol;

public class Instrumentalist implements Performer {

    private String song;

    private Instrument instrument;

    public Instrumentalist() {
    }

    public void perform() throws PerformanceException {
        System.out.print("Playing " + song + " : ");
        instrument.play();
    }

    public void setSong(String song) { // 注入歌曲
        this.song = song;
    }

    public String getSong() {
        return song;
    }

    public String screamSong() {
        return song;
    }

    public void setInstrument(Instrument instrument) { // 注入樂器
        this.instrument = instrument;
    }
}

配置文件中需要為Instrumentalist 注入這兩個屬性的值。

注入簡單值

使用<property>標簽可以通過調用屬性的setter方法為Bean注入屬性,而類似的<constructor-arg>是通過構造函數注入的。

<bean id="kenny" class="com.springinaction.springidol.Instrumentalist">
    <property name="song" value="Happy" />
</bean>

<property>元素會指示Spring調用setSong()方法將song屬性的值設置為"Happy"。

注意value的屬性值可以指定數值型(int、float、Double等)以及boolean等,Spring在調用set方法前會自動根據類型進行轉換。

引用其他Bean

在演奏家kenny中需要一個樂器,那么我們就可以為其引用一個實現了Instrument接口的樂器。

<bean id="saxphone" class="com.springinaction.springidol.Saxophone" />
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist">
    <property name="song" value="Happy" />
    <property name="instrument" ref="saxphone"></property>
</bean>

通過Performer接口引用一個參賽者,就可以產生任意類型的參賽者進行表演,面向接口編程依賴注入實現了松耦合。

注入內部Bean

前文中,演奏家使用的instrument是其他Bean都可以進行共用的,若要獨用一個類,那么可以聲明一個類為內部Bean

<bean id="kenny" class="com.springinaction.springidol.Instrumentalist">
    <property name="song" value="Happy" />
    <property name="instrument">
        <bean class="com.springinaction.springidol.Saxophone" />
    </property>
</bean>

如上面的代碼,通過直接聲明一個<bean>元素作為<property>元素的子節點。內部Bean不限于setter注入,也可以通過構造方法注入。

內部Bean沒有Id屬性,它們不能被復用,內部Bean僅適用于一次注入,而不能被其他Bean引用。

裝配集合

集合元素 用途
<list> 裝配list類型的數據,允許重復
<set> 裝配set類型的數據,不允許重復
<map> 裝配map類型的數據,key和value可以是任意類型
<props> 裝配properties類型的數據,key和value必須是String類型

下面一個演奏家可以演奏多種樂器:

package com.springinaction.springidol;

import java.util.Collection;

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

    public void perform() throws PerformanceException {
        // 遍歷演奏各個樂器
        for (Instrument instrument : instruments) {
            instrument.play();
        }
    }

    private Collection<Instrument> instruments;

    public void setInstruments(Collection<Instrument> instruments) {// 注入instruments集合
        this.instruments = instruments;
    }
}

裝配List、Set和Array

可以使用下面的方式裝配List,也可以使用<set>來裝配:

<bean id="hank" class="com.springinaction.springidol.OneManBand">
    <property name="instruments">
        <list>
            <ref bean="guitar"/>
            <ref bean="cymbal"/>
            <ref bean="harmonica"/>
        </list>
    </property>
</bean>

裝配Map

當OneManBand表演時,perform()方法可以把樂器(instrument)的音符打印出來,我們還想知道每個音符是由哪個樂器產生的,因此需要做以下改變:

package com.springinaction.springidol;

import java.util.Map;

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

    public void perform() throws PerformanceException {
        for (String key : instruments.keySet()) {
            System.out.print(key + " : ");
            Instrument instrument = instruments.get(key);
            instrument.play();
        }
    }

    private Map<String, Instrument> instruments;

    public void setInstruments(Map<String, Instrument> instruments) {// 以map類型注入instruments
        this.instruments = instruments;
    }
}

Spring配置map注入:

<bean id="hankk" class="com.springinaction.springidol.OneManBandMap">
    <property name="instruments">
        <map>
            <entry key="GUITAR" value-ref="guitar" />
            <entry key="CYMBAL" value-ref="cymbal" />
            <entry key="HARMONICA" value-ref="harmonica" />
        </map>
    </property>
</bean>

<map>中的<entry>元素由一個鍵和一個值組成,鍵和值可以是簡單類型,也可以是其他Bean的引用:

屬性 用途
key 指定map中entry的為String
key-ref 指定map中entry的為Spring山下文中其他Bean的引用
value 指定map中entry的為String
value-ref 指定map中entry的為Spring山下文中其他Bean的引用

裝配Properties集合

若OneManBandMap中的Instrument屬性所配置的Map的每一個entry的鍵和值都是String類型,可以使用java.util.Properties來代替Map:

<bean id="hank" class="com.springinaction.springidol.OneManBand">
    <property name="instruments">
        <props>
            <prop key="GUITAR">Strum Strum Strum</prop>
            <prop key="CYMBAL">Crush Crush Crush</prop>
            <prop key="HARMONICA">Hum Hum Hum</prop>
        </props>
    </property>
</bean>
  • <property>元素用于把值或者Bean引用注入到Bean的屬性中;
  • <props>用于定義一個java.util.Properties類型的集合值;
  • <prop>用于定義<props>集合的一個成員

裝配空值

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

使用表達式裝配

如果為屬性裝配的值只有在運行期間才能獲取,那該如何實現?
Spring表達式語言( Spring Expression Language , SpEL),可以通過運行期執行的表達式將值裝配到Bean的屬性或者構造器參數中,其特性有:

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

SpEL基本用法

字面值

比如<property name="count" value="#{5}"/>#{ }標記會提示Spring這是個SpEL表達式。

可以與非SpEL表達式混用:<property name="message" value="The value is #{5}"/>

另外,浮點型、科學計數法、布爾型(true和false)也可以直接使用。

字符串使用時,需要用單引號或者雙引號括起。

引用Bean、Properties和方法(避免空指針)

新聲明一個id為carl的模仿者,Kenny唱什么他就唱什么:

<bean id="carl" class="com.springinaction.springidol.Instrumentalist">
    <property name="song" value="#{kenny.song}" />
</bean>

注入到Carl的song屬性的表達式是由兩部分組成的,第一部分(kenny)指向了kenny的Bean,第二部分(song)指向了kenny Bean的song屬性,其實等價于下面的代碼:

Instrumentalist carl = new Instrumentalist();
carl.setSong(kenny.getSong());

還可以調用其他Bean的方法:
<property name="song" value="#{songSelector.selectSong()}"/>
<property name="song" value="#{songSelector.selectSong().toUpperCase()}"/>

如果selectSong()返回一個null,那么SpEL會拋出空指針異常,可以采用下面的方法避免:
<property name="song" value="#{songSelector.selectSong()?.toUpperCase()}"/>
使用?.來代替.來訪問toUpperCase()方法,訪問之前會確保左邊項不為null,若為null就不會再繼續調用。

操作類

可以使用T()運算符調用類作用域的方法和常量。比如:
<property name="multiplier" value="#{T(java.lang.Math).PI}"/>
<property name="randomNumber" value="#{T(java.lang.Math).random()}"/>

在SpEL值上進行操作

運算符類型 運算符 例子
算術運算 +, -, *, /, %, ^ #{T(java.lang.Math).PI * circle.radius ^ 2}
關系運算 <, >, ==, <=, >=, lt,gt, eq, le, ge #{counter.total == 100}
邏輯運算 and, or, not(或!) #{!product.available}
條件運算 ?: (ternary), ?: (Elvis) #{m>=n?m:n}
正則表達式 matches #{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.com'}

如果覺得有用,歡迎關注我的微信,有問題可以直接交流:

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

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,787評論 18 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,899評論 6 342
  • 本章內容: 聲明Bean 構造器注入和Setter方法注入 裝配Bean 控制bean的創建和銷毀 任何一個成功的...
    謝隨安閱讀 1,655評論 0 9
  • 文章作者:Tyan博客:noahsnail.com 3.4 依賴 標準企業應用不會由一個對象(或Spring用語中...
    SnailTyan閱讀 1,203評論 0 1
  • 對你動了真情, 我的心湖再也無法平靜。 欣賞著你美麗的姿容, 感受著你美好的心靈, 只想與你一起一生一世一心一意相...
    寫給遠方的她閱讀 236評論 0 0