創建應用對象之間協作關系的行為通常被稱作裝配
(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-method
和destroy-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-method
和default-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'} |
如果覺得有用,歡迎關注我的微信,有問題可以直接交流:
