创建 XML 配置规范
在使用 XML 为 Spring 装配之前,需要创建一个新的配置规范。在 XML 配置中,要创建一个 XML 文件,并且要以 <beans> 元素为根元素。 最为简单的 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">
<!-- 在这里进行详细的配置 -->
</beans>
用来装配 bean 的最基本的 XML 元素包含在 Spring-beans 模式之中,在上面这个 XML文件中,它被定义为根命名空间。<beans> 是该模式的一个元素,它是所有 Spring 配置文件的根元素。
声明一个简单的 <bean>
首先,创建一个 CompactDisc 接口及其实现类 SgtPeppers。
package org.example.xmlconfig;
public interface CompactDisc {
void play();
}
package org.example.xmlconfig;
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";
@Override
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
要在基于 XML 的 Spring 配置中声明一个 bean,需要使用 Spring-beans 模式中的另外一个元素:<bean>。<bean> 元素类似于配置类中的 @Bean 注解。可以按照下面的方式声明 CompactDisc bean:
<bean class="org.example.xmlconfig.SgtPeppers"/>
这样就创建了一个简单的 bean,通过 class 属性来指定创建这个 bean 的类,并且要使用全限定的类名。
因为没有明确给定 ID,所以这个 bean 将会根据全限定类名来进行命名。在上面的配置中,bean 的 ID 将会是org.example.xmlconfig.SgtPeppers#0。其中,#0是一个计数的形式,用来区分相同的类型的其他 bean。如果声明了另一个 SgtPeppers,并且没有明确进行标识,那么它自动得到的 ID 将会是org.example.xmlconfig.SgtPeppers#1。
另外,还可以借助 id 属性,为每个 bean 设置一个名字,方便在其他地方引用:
<bean id="compactDisc" class="org.example.xmlconfig.SgtPeppers"/>
这样就不再需要直接创建 SgtPeppers 的实例了。当 Spring 发现这个 <bean> 元素时,它将会调用 SgtPeppers 的默认构造器来创建 bean。
借助构造器注入初始化 bean
在 Spring XML 配置中,只要一种声明 bean 的方式:使用 <bean> 元素指定 class 属性。Spring 会从中获取必要的信息来创建 bean。
但是,在 XML 中声明 DI 时,构造器注入有两种基本的配置方案可供选择:
- <constructor-arg> 元素
- 使用 Spring 3.0 所引入的 c-命名空间
构造器注入 bean 引用
首先,创建一个 MediaPlayer 的接口及其实现类 CDPlayer。
package org.example.xmlconfig;
public interface MediaPlayer {
void play();
}
package org.example.xmlconfig;
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
public CDPlayer(CompactDisc cd){
this.cd = cd;
}
@Override
public void play() {
cd.play();
}
}
按照上面的定义,CDPlayer bean 有一个接受 CompactDisc 类型的构造器。前面我们已经声明了 SgtPeppers bean,并且 SgtPeppers 类实现了 CompactDisc 接口,所以我们已经有了一个可以注入到 CDPlayer bean 中的 bean。所以我们需要做的就是在 XML 中声明 CDPlayer 并通过 ID 引用 SgtPeppers:
<bean id="compactDisc" class="org.example.xmlconfig.SgtPeppers"/>
<bean id="cdPlayer" class="org.example.xmlconfig.CDPlayer">
<constructor-arg ref="compactDisc"/>
</bean>
当 Spring 遇到这个 <bean> 元素时,它会创建一个 CDPlayer 实例。<constructor-arg> 元素会告诉 Spring 要将一个 ID 为 compactDisc 的 bean 引用传递到 CDPlayer 的构造器中。
作为替代方案,也可以使用 Spring 的 c-命名空间。c-命名空间是在 Spring 3.0 中引入的,它是在 XML 中更加简洁地描述构造器参数的方式。要使用它的话,必须要在 XML 的顶部声明其模式,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入c-命名空间 -->
<beans xmlns:c="http://www.springframework.org/schema/c"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/bean http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
在 c-命名空间和模式声明之后,就可以使用它来声明构造器参数了,如下所示:
<bean id="cdPlayer" class="org.example.xmlconfig.CDPlayer" c:cd-ref="compactDisc"/>
这里使用了 c-命名空间来声明构造器参数,它作为 <bean> 元素的一个属性,以c:开头,也就是命名空间的前缀。然后就是要装配的构造器参数名,这里是cd,在此之后是-ref,这是一个命名的约定,它会告诉 Spring 正在装配的是一个 bean 的引用,=后面跟着的就是要引用的 bean 的名称(ID)。
另一种方式是不引用构造器参数的名称,而是使用在整个参数列表中的位置信息:
<bean id="cdPlayer" class="org.example.xmlconfig.CDPlayer" c:_0-ref="compactDisc"/>
这里将参数的名称cd替换成了0,也就是参数的索引。因为在 XML 中不允许数字作为属性的第一个字符,因此必须在前面加一个下划线_。
当构造器只有一个参数时,可以不标识参数位置:
<bean id="cdPlayer" class="org.example.xmlconfig.CDPlayer" c:_-ref="compactDisc"/>
将字面量注入到构造器中
依赖注入通常指的是类型的装配,也就是将对象的引用装配到依赖于它们的其他对象之中。有时候需要用一个字面量值来配置对象。
首先,新建一个 CompactDisc 的实现类 BlankDisc,如下所示:
package org.example.xmlconfig;
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
public BlankDisc(String title, String artist){
this.title = title;
this.artist = artist;
}
@Override
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
在 SgtPeppers 类中,title 和 artist 都是硬编码,但是 BlankDisc 类与之不同,它更加灵活。可以将 Spring XML 配置文件中已有的 SgtPeppers 类替换成 BlankDisc 类:
<bean id="compactDisc" class="org.example.xmlconfig.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
<constructor-arg value="The Beatles"/>
</bean>
这里再次使用 <constructor-arg> 元素进行构造器参数注入。但是并没有使用ref属性来引用其他的 bean,而是使用了value属性,通过该属性表明给定的值要以字面量的形式注入到构造器中。
如果要使用 c-命名空间的话,有两种方案。一种是引用构造器参数的名字:
<bean id="compactDisc" class="org.example.xmlconfig.BlankDisc"
c:title="Sgt. Pepper's Lonely Hearts Club Band"
c:artist="The Beatles"/>
另一种是使用参数的索引:
<bean id="compactDisc" class="org.example.xmlconfig.BlankDisc"
c:_0="Sgt. Pepper's Lonely Hearts Club Band"
c:_1="The Beatles"/>
可以看到,装配字面量与装配引用的区别在于属性名中去掉了-ref后缀。
装配集合
在装配 bean 引用和字面量值方面,<constructor-arg> 和 c-命名空间的功能是相同的。但是,将集合装配到构造器参数中,c-命名空间是无法做到的。
首先,修改 BlankDisc 类,添加一个 List 类型的属性 tracks 并作为其构造器的参数,代码如下:
package org.example.xmlconfig;
import java.util.List;
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public BlankDisc(String title, String artist, List<String> tracks){
this.title = title;
this.artist = artist;
this.tracks = tracks
}
@Override
public void play() {
System.out.println("Playing " + title + " by " + artist);
for (String track : tracks){
System.out.println("-Track: " + track);
}
}
}
最简单的方法是将其设置为 null。因为它是一个构造器参数,所以必须要声明它。可以采用如下的方式传递 null 值给它:
<bean id="compactDisc" class="org.example.xmlconfig.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
<constructor-arg value="The Beatles"/>
<!--将 tracks 属性的值设置为 null-->
<constructor-arg><null/></constructor-arg>
</bean>
<null> 元素所做的事情就是将 null 传递给构造函数。但是当调用 play() 方法时,会引发 NullPointerException 异常,因此该方法并不是很理想。
另一种方法使用 <list> 元素和 <value> 元素。 首先,使用 <list> 元素将其声明为一个列表,然后使用 <value> 元素来指定列表中每个元素,代码如下:
<bean id="compactDisc" class="org.example.xmlconfig.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band"/>
<constructor-arg value="The Beatles"/>
<constructor-arg>
<list>
<value>Sgt. Pepper's Lonely Hearts Club band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
</list>
</constructor-arg>
</bean>
其中,<list> 元素是 <constructor-arg> 的子元素,这表明一个包含值的列表将会传递到构造器中。
也可以使用 <ref> 元素替代 <value> 元素,来实现 bena 引用列表的装配。例如,有一个 Discography 类,它的构造器如下所示:
public Discography(String artist, List<CompactDisc> cds){
...
}
那么,可以采用如下的方式配置 Discography bean:
<bean id="discography" class="org.example.xmlconfig.Discography">
<constructor-arg value="The Beatles"/>
<constructor-arg>
<list>
<ref bean="compactDisc"/>
<ref bean="whiteAlbun"/>
<ref bean="hardDaysNight"/>
<ref bean="revolver"/>
</list>
</constructor-arg>
</bean>
当构造器的参数是 java.util.List 类型时,使用 <list> 元素是合情合理的。同样的,当构造器的参数是 Java.util.Set 类型时,可以按照相同的方式使用 <set> 元素:
<bean id="discography" class="org.example.xmlconfig.Discography">
<constructor-arg value="The Beatles"/>
<constructor-arg>
<set>
<value>Sgt. Pepper's Lonely Hearts Club band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
</set>
</constructor-arg>
</bean>
<set> 和 <list> 元素的区别不大,其中最重要的不同在于当 Spring 创建要装配的集合时,所创建的是 java.util.Set 还是 java.util.List。如果是 Set 的话,所有重复的值都会被忽略,存放顺序也不会得到保证。
设置属性
前面的 CDPlayer 类和 BlankDisc 类都是通过构造器注入的,没有使用属性的 Setter 方法。下面介绍使用 Spring XML 实现属性注入。
首先,修改 CDPlayer 类的代码如下:
package org.example.xmlconfig;
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
public void setCompactDisc(CompactDisc compactDisc){
this.compactDisc = compactDisc;
}
@Override
public void play() {
compactDisc.play();
}
}
这样 CDPlayer 就没有任何的构造器(除了隐含的默认构造器),它也没有任何的强依赖。因此,可以采用如下的方式将其声明为 Spring bean:
<bean id="cdPlayer" class="org.example.xmlconfig.CDPlayer"/>
Spring 在创建 bean 的时候不会有任何问题,但是 CDPlayer 的属性 compactDisc 为 null,在引用时就会出现 NullPointerException 异常。不过按照如下的方式修改 XML,就能解决问题:
<bean id="cdPlayer" class="org.example.xmlconfig.CDPlayer">
<property name="compactDisc" ref="compactDisc"/>
</bean>
<property> 元素为属性的 Setter 方法所提供的功能与 <constructor-arg> 元素为构造器提供的功能是一样的。在这里它通过ref属性引用了 ID 为 compactDisc 的 bean,并 setCompactDisc() 方法将其注入到 compactDisc 属性中。
Spring 也为 <property> 元素提供了 p-命名空间作为替代方案。为了开启 p-命名空间,必须要在 XML 文件中进行声明:
<?xml version="1.0" encoding="UTF-8"?>
<!--声明 p-命名空间-->
<beans xmlns:p="http://www.springframework.org/schema/p"
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">
然后,就可以使用 p-命名空间,按照以下的方式装配 compactDisc 属性:
<bean id="cdPlayer" class="org.example.xmlconfig.CDPlayer" p:compactDisc-ref="compactDisc"/>
p-命名空间中属性所遵循的命名约定与 c-命名空间中的属性类型。首先,属性的名字使用了p:前缀,表明所设置的是一个属性,跟在后面的是要注入的属性名compactDisc,最后,属性的名称以-ref结尾,这表示 Spring 要进行装配的是引用,而不是字面量。
将字面量注入到属性中
属性也可以注入字面量,这与构造器参数非常类似。首先,修改 BlankDisc 的代码:
package org.example.xmlconfig;
import java.util.List;
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public void setTitle(String title) {
this.title = title;
}
public void setArtist(String artist) {
this.artist = artist;
}
public void setTracks(List<String> tracks) {
this.tracks = tracks;
}
@Override
public void play() {
System.out.println("Playing " + title + " by " + artist);
for (String track : tracks){
System.out.println("-Track: " + track);
}
}
}
这样就可以按照如下的方式创建一个 BlankDisc bean,它的所有属性都是 null:
<bean id="compactDisc" class="org.example.xmlconfig.BlankDisc"/>
当然,在装配 bean 的时候不设置这些属性,那么,在运行 play() 方法时,就会抛出 NullPointerException 异常。所以,需要装配这些属性,可以借助 <property> 元素的 value 属性实现该功能:
<bean id="compactDisc" class="org.example.xmlconfig.BlankDisc">
<property name="title" value="Sgt. Pepper's Lonely Hearts Club band"/>
<property name="artist" value="The Beatles"/>
<property name="tracks">
<list>
<value>Sgt. Pepper's Lonely Hearts Club band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Holer</value>
</list>
</property>
</bean>
这里,除了使用 <property> 元素的 value 属性来设置 title 和 artist,还使用了内嵌的 <list> 元素来设置 tracks 属性,这与 <constructor-arg> 装配 tracks 是完全一样的。
另外一种可选方案就是使用 p-命名空间的属性来完成该功能:
<bean id="compactDisc" class="org.example.xmlconfig.BlankDisc"
p:title="Sgt. Pepper's Lonely Hearts Club band"
p:artist="The Beatles">
<property name="tracks">
<list>
<value>Sgt. Pepper's Lonely Hearts Club band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Holer</value>
</list>
</property>
</bean>
与 c-命名空间一样,装配 bean 引用与装配字面量唯一的区别在于是否带有-ref后缀。如果没有-ref后缀的话,所装配的就是字面量。
需要注意的是,不能使用 p-命名空间来装配集合,没有便利的方式使用 p-命名空间来指定一个值(或 bean 引用)的列表。但是,可以使用 Spring util-命名空间中的一些功能来简化 BlankDisc bean。
首先,需要在 XML 中声明 util-命名空间及其模式:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:util="http://www.springframework.org/schema/util"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
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
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
</beans>
util-命名空间所提供的功能之一就是 <util:list> 元素,它会创建一个列表的 bean。借助 <util:list> 可以将 tracks 列表转移到 BlankDisc bean 之外,并声明到单独的 bean 之中,如下所示:
<util:list id="trackList">
<value>Sgt. Pepper's Lonely Hearts Club band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Holer</value>
</util:list>
现在就可以像使用其他 bean 那样将 tracks 列表 bean 注入到 BlankDisc bean 的 tracks 属性中:
<bean id="compactDisc" class="org.example.xmlconfig.BlankDisc"
p:title="Sgt. Pepper's Lonely Hearts Club band"
p:artist="The Beatles"
p:tracks-ref="trackList">
</bean>
<util:list> 只是 util-命名空间中的多个元素之一。util-命名空间提供的所有元素如下表所示:
| 元素 | 描述 |
|---|---|
| <util:constant> | 引用某个类型的 public static 域,并将其暴露为 bean |
| <util:list> | 创建一个 java.util.List 类型的 bean,其中包含值或者引用 |
| <util:map> | 创建一个 java.util.Map 类型的 bean,其中包含值或者引用 |
| <util:properties> | 创建一个 java.util.Properties 类型的 bean |
| <util:property-path> | 引用一个 bean 的属性(或内嵌属性),并将其暴露为 bean |
| <util:set> | 创建一个 java.util.Set 类型的 bean,其中包含值或引用 |