创建应用对象之间协作关系的行为通常被称作装配
(Wiring),这也是依赖注入的本质。
声明Bean
创建Spring配置
Spring容器提供了两种配置Bean的方式,其一是使用XML文件作为配置文件,其二是基于Java注解的配置方式。
以下是一个典型的Spring XML配置文件:1
2
3
4
5
6
7
8"1.0" encoding="UTF-8" xml version=
<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>
在
命名空间 | 用途 |
---|---|
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
1 | package com.springinaction.springidol; |
1 | <bean id="duke" class="com.springinaction.springidol.Juggler"></bean> |
<bean>
元素是Spring中最基本的配置单元,通过该元素Spring将创建一个对象。当Spring容器加载该Bean时,Spring将使用默认的构造器来实例化该Bean,实际上,duke会使用如下代码来创建:new com.springinaction.springidol.Juggler();
通过构造器注入
让bean使用另外一个构造方法:
1 | <bean id="duke" class="com.springinaction.springidol.Juggler"> |
在构造Bean的时候,可以使用<constructor-arg>
标签来告诉Spring额外的信息,这样Spring就不再会使用默认的构造器来实例化该Bean。
测试代码:
1 | package com.springinaction.springidol; |
通过运行结果JUGGLING 15 BEANBAGS
可以15被注入到了构造器中。
通过构造器注入对象引用
现在需要一个新的类PoeticJuggler
,该类需要Poem
类作为其参数并通过构造器注入。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package 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;
}
public void perform() throws PerformanceException {
super.perform();
System.out.println("While reciting...");
poem.recite();
}
}
在配置文件中就需要声明一个 Poem
的类,并将其注入到PoeticJuggler
中:
1 | <bean id="sonnet29" class="com.springinaction.springidol.Sonnet29"></bean> |
这里使用ref属性来将Id为sonnet29的Bean引用传递给构造器,当Spring遇到sonnet29和poeticDuke的bean声明时,所执行的逻辑脚本将是:
1 | Poem sonnet29 = new Sonnet29(); |
通过工厂方法创建Bean
在没有公开的构造方法时,可以通过工厂方法来创建Bean,及factory-method
属性来装配工厂创建的Bean。
比如Stage是一个没有公开构造方法的类,但是可以通过getInstance获取其实例,那么可以通过下面的配置方式:1
2<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上下文中才有效 |
配置方法,设置1
<bean id="sonnet29" class="com.springinaction.springidol.Sonnet29" scope="prototype"/>
Spring的单例只能保证在每个应用上下文中只有一个Bean的实例,你也可以通过定义多个
初始化和销毁Bean
可以为Bean定义初始化和销毁操作,只需使用init-method
和destroy-method
参数来配置
比如,舞台(Auditorium)需要在表演开始前开灯(turnOnLights),在结束时关灯(turnOffLights),那么就可以做下面的声明:1
2<bean id="auditorium" class="com.springinaction.springidol.Auditorium"
init-method="turnOnLights" destroy-method="turnOffLights" />
默认的init-method和destroy-method
可以使用default-init-method
和default-destroy-method
为上下文中所有的Bean设置共同的初始化和销毁方法。
注入Bean属性
Spring可以借助属性的set方法来配置属性的值,以实现setter方式的注入。
下面是一个音乐家(Instrumentalist)类,它演奏时需要歌曲(song)和乐器(instrument)两个属性。
1 | package com.springinaction.springidol; |
配置文件中需要为Instrumentalist 注入这两个属性的值。
注入简单值
使用<property>
标签可以通过调用属性的setter方法为Bean注入属性,而类似的<constructor-arg>
是通过构造函数注入的。1
2
3<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接口的乐器。
1 | <bean id="saxphone" class="com.springinaction.springidol.Saxophone" /> |
通过Performer接口引用一个参赛者,就可以产生任意类型的参赛者进行表演,面向接口编程
和依赖注入
实现了松耦合。
注入内部Bean
前文中,演奏家使用的instrument是其他Bean都可以进行共用的,若要独用一个类,那么可以声明一个类为内部Bean:1
2
3
4
5
6<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没有Id属性,它们不能被复用,内部Bean仅适用于一次注入,而不能被其他Bean引用。
装配集合
集合元素 | 用途 |
---|---|
<list> |
装配list类型的数据,允许重复 |
<set> |
装配set类型的数据,不允许重复 |
<map> |
装配map类型的数据,key和value可以是任意类型 |
<props> |
装配properties类型的数据,key和value必须是String类型 |
下面一个演奏家可以演奏多种乐器:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package 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,也可以使用1
2
3
4
5
6
7
8
9<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)的音符打印出来,我们还想知道每个音符是由哪个乐器产生的,因此需要做以下改变:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package 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注入:1
2
3
4
5
6
7
8
9<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>
属性 | 用途 |
---|---|
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:1
2
3
4
5
6
7
8
9<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>
元素用于把值或者Bean引用注入到Bean的属性中; 用于定义一个 java.util.Properties
类型的集合值;用于定义 集合的一个成员
装配空值
1 | <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唱什么他就唱什么:1
2
3<bean id="carl" class="com.springinaction.springidol.Instrumentalist">
<property name="song" value="#{kenny.song}" />
</bean>
注入到Carl的song属性的表达式是由两部分组成的,第一部分(kenny
)指向了kenny的Bean,第二部分(song
)指向了kenny Bean的song属性,其实等价于下面的代码:1
2Instrumentalist 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’} |