使用 XML 装配 bean

74 阅读10分钟

创建 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,其中包含值或引用