第二章 装配Bean

2.1 Spring 配置的可选方案


Spring容器负责创建应用程序中的bean并通过DI来协调这些对象之间的关系。描述bean如何配置时,Spring提供了三种主要的装配机制:

  • 在XML中进行显示配置
  • 在Java中进行显示配置
  • 隐式的bean发现机制和自动装配

Spring的配置风格是可以相互搭配的,你可以一部分使用XML,一部分使用Java配置,剩余的让Spring去自动发现。建议是尽可能的使用自动配置,显示配置越少越好。当必须使用显示配置的时候,推荐使用类型安全并且比XML更强大的JavaConfig。只有当你想要便利的使用XML命名空间,并且JavaConfig没有相应的实现时,才应该使用XML。


2.2 自动化装配bean


Spring从两个角度来实现自动装配:

  • 组件扫描(Component scanning):Spring会自动发现应用上下文中所创建的bean。
  • 自动装配(autowiring):Spring自动满足bean之间的依赖。

例如:

  • 我们创建了几个bean,它们代表了一个音响系统中的组件。首先,要创建CompactDisc类,Spring会发现它并将其创建为一个bean。然后,会创建一个CDPlayer类,让Spring发现它,并将CompactDisc bean注入进来。

2.2.1 创建可以被发现的bean


首先定义一个CD的接口:

package soundsystem;

public interface CompactDisc {

    void play();

}

我们还需要一个实现:

package soundsystem;

import org.springframework.stereotype.Component;

@Component
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);
    }

}

SgtPeppers类使用了@Component注解,这个注解表明该类是组建类,并告诉Spring要为这个类创建bean。

不过,组件扫描默认不启用,我们需要显示的配置一下Spring。让它去寻找带有@Component注解的类,并为其创建bean。

package soundsystem;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class CDPlayerConfig {

}

**如果没有其他配置的话,@ComponentScan会默认扫描与配置类相同的包。**XML配置可以使用Spring context命名空间的<context:component-scan>元素。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:Context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/aop/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <Context:component-scan base-package="soundsystem" />

</beans>

然后测试组件是否能被扫描出来:

package soundsystem;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {

    @Autowired
    private CompactDisc cd;

    @Test
    public void cdShouldNotBeNull() {
        assertNotNull(cd);
    }

}

2.2.2 为组件扫描的bean命名


Spring应用上下文中所有的bean都会给定一个ID,尽管我们没有明确地为SgtPeppersbean设置ID,但Spring会根据类名为其指定一个ID。具体来讲,这个bean所给定的ID为sgtPeppers,也就是将类名的第一个字母变为小写。

如果想设置为不同的ID:

@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
    ...
}

还可以使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named来注解设置ID:

import javax.inject.Named;

@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
    ...
}

2.23 设置组件扫描的基础包


按照默认规则,它会以配置类所在的包作为基础包(base package)来扫描组件。如果想要扫描多个基础包,你需要做的就是在@ComponentScan的value属性中指定包的名称

@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig {}

如果你想更加清晰地表明你所设置的是基础包,那么你可以通过basePackages属性进行配置:

@Configuration
@ComponentScan(basePackages="soundsystem")
public class CDPlayerConfig {}

当然,还可以指定多个包:

@Configuration
@ComponentScan(basePackages={"soundsystem", "video"})
public class CDPlayerConfig {}

不过,使用String不安全,在进行代码重构时,就可能会发生错误。所以**@ComponentScan还提供了另外一种方法,那就是指定类或接口:

@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {}

2.2.4 通过为bean添加注解实现自动装配


自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
考虑CDPlayer类。它的构造器上添加了@Autowired注解,这表明当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc类型的bean。

package soundsystem;

public interface MediaPlayer {

    public void play();

}
package soundsystem;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CDPlayer implements MediaPlayer {

    private CompactDisc cd;

    @Autowired
    public CDPlayer(CompactDisc cd) {
        this.cd = cd;
    }

    @Override
    public void play() {
        cd.play();
    }

}

@Autowired注解不仅能够用在构造器上,还能用在属性的Setter方法上。比如说,如果CDPlayer有一个setCompactDisc()方法,那么可以采用如下的注解形式进行自动装配:

@AutoWired
public void setCompactDisc(CompactDisc cd) {
    this.cd = cd;
}

在Spring初始化bean之后,它会尽可能得去满足bean的依赖,在本例中,依赖是通过带有@Autowired注解的方法进行声明的,也就是setCompactDisc()实际上,Setter方法并没有什么特殊之处。@Autowired注解可以用在类的任何方法上。假设CDPlayer类有一个insertDisc()方法,那么@Autowired能够像在setCompactDisc()上那样,发挥完全相同的作用。

不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。

如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowiredrequired属性设置为false。

@AutoWired(required=false)

将required属性设置为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。但是,把required属性设置为false时,你需要谨慎对待。如果在你的代码中没有进行null检查的话,这个处于未装配状态的属性有可能会出现NullPointerException
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配。@AutoWired是Spring特有的注解,如果不愿意在代码中到处使用Spring特定的注解,可以考虑使用@Inject来替换:

package soundsystem;

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class CDPlayer implements MediaPlayer {

    private CompactDisc cd;

    @Inject
    public CDPlayer(CompactDisc cd) {
        this.cd = cd;
    }

    @Override
    public void play() {
        cd.play();
    }

}

@Inject@Named有一点点差异,不过大多数情况下都可以互换。


2.2.5 验证自动装配


修改CDPlayerTest:

package soundsystem;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {

    @Autowired
    private MediaPlayer player;

    @Autowired
    private CompactDisc cd;

    @Test
    public void cdShouldNotBeNull() {
        assertNotNull(cd);
    }

    @Test
    public void play() {
        player.play();
    }

}

2.3 通过Java代码装配bean


尽管在很多场景下通过组件扫描和自动装配实现Spring的自动化配置是更为推荐的方式,但有时候自动化配置的方案行不通,因此需要明确配置Spring。比如说,你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component@Autowired注解的,因此就不能使用自动化装配的方案了。在这种情况下,你必须要采用显式装配的方式。在进行显式配置的时候,有两种可选方案:JavaXML
在进行显式配置时,JavaConfig是更好的方案,因为它更为强大、类型安全并且对重构友好。因为它就是Java代码,就像应用程序中的其他Java代码一样。同时,JavaConfig与其他的Java代码又有所区别,在概念上,它与应用程序中的业务逻辑和领域代码是不同的。尽管它与其他的组件一样都使用相同的语言进行表述,但JavaConfig是配置代码。这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。尽管不是必须的,但通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了。


2.3.1 创建配置类


创建JavaConfig类的关键在于为其添加@Configuration注解,@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。到此为止,我们都是依赖组件扫描来发现Spring应该创建的bean。尽管我们可以同时使用组件扫描和显式配置,我们为了更加关注于显式配置,因此将CDPlayerConfig的@ComponentScan注解移除掉。
移除了@ComponentScan注解,此时的CDPlayerConfig类就没有任何作用了。如果你现在运行CDPlayerTest的话,测试会失败,并且会出现BeanCreationException异常。测试期望被注入CDPlayer和CompactDisc,但是这些bean根本就没有创建,因为组件扫描不会发现它们。


2.3.2 声明简单的bean


要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解。比方说,下面的代码声明了CompactDisc bean。

  • @Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。
  • 默认情况下,bean的ID与带有@Bean注解的方法名是一样的。如果你想为其设置成一个不同的名字的话,那么可以重命名该方法,也可以通过name属性指定一个不同的名字:
package soundsystem;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CDPlayerConfig {

    @Bean(name="lonely HeartsClubBand")
    public CompactDisc sgtPeppers() {
        return new SgtPeppers();
    }

}

2.3.3 借助JavaConfig实现注入


我们前面所声明的CompactDisc bean是非常简单的,它自身没有其他的依赖。但现在,我们需要声明CDPlayerbean,它依赖于CompactDisc。在JavaConfig中,要如何将它们装配在一起呢?
在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。例如,下面就是一种声明CDPlayer的可行方案:

package soundsystem;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CDPlayerConfig {

    @Bean(name="lonely HeartsClubBand")
    public CompactDisc sgtPeppers() {
        return new SgtPeppers();
    }

    @Bean
    public CDPlayer cdPlayer() {
        return new CDPlayer(sgtPeppers());
    }

}

cdPlayer()方法像sgtPeppers()方法一样,同样使用了@Bean注解,这表明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。所创建的bean ID为cdPlayer,与方法的名字相同。cdPlayer()的方法体与sgtPeppers()稍微有些区别。在这里并没有使用默认的构造器构建实例,而是调用了需要传入CompactDisc对象的构造器来创建CDPlayer实例。
看起来,CompactDisc是通过调用sgtPeppers()得到的,但情况并非完全如此。因为sgtPeppers()方法上添加了@Bean注解,Spring将会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用。

假如对sgtPeppers()的调用就像其他的Java方法调用一样的话,那么每个CDPlayer实例都会有一个自己特有的SgtPeppers实例。如果我们讨论的是实际的CD播放器和CD光盘的话,这么做是有意义的。你有两台CD播放器,在物理上并没有办法将同一张CD光盘放到两个CD播放器中。但是,在软件领域中,我们完全可以将同一个SgtPeppers实例注入到任意数量的其他bean之中。默认情况下,Spring中的bean都是单例的,我们并没有必要为第二个CDPlayer bean创建完全相同的SgtPeppers实例。所以,Spring会拦截对sgtPeppers()的调用并确保返回的是Spring所创建的bean,也就是Spring本身在调用sgtPeppers()时所创建的CompactDiscbean。因此,两个CDPlayer bean会得到相同的SgtPeppers实例。
可以看到,通过调用方法来引用bean的方式有点令人困惑。其实还有一种理解起来更为简单的方式:

@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
    return new CDPlayer(compactDisc);
}

在这里,cdPlayer()方法请求一个CompactDisc作为参数。当Spring调用cdPlayer()创建CDPlayer bean的时候,它会自动装配一个CompactDisc到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer()方法也能够将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的@Bean方法。
通过这种方式引用其他的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置类之中。在这里甚至没有要求CompactDisc必须要在JavaConfig中声明,实际上它可以通过组件扫描功能自动发现或者通过XML来进行配置。你可以将配置分散到多个配置类、XML文件以及自动扫描和装配bean之中,只要功能完整健全即可。不管CompactDisc是采用什么方式创建出来的,Spring都会将其传入到配置方法中,并用来创建CDPlayer bean。
另外,需要提醒的是,我们在这里使用CDPlayer的构造器实现了DI功能,但是我们完全可以采用其他风格的DI配置。比如说,如果你想通过
Setter方法注入CompactDisc的话,那么代码看起来应该是这样的:

@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
    CDPlayer player = new CDPlayer(compactDisc);
    player.setCompactDisc(compactDisc);
    return player;
}

2.4 通过XML装配bean


2.4.1 创建XML配置规范


在使用JavaConfig的时候,这意味着要创建一个带有@Configuration注解的类,而在XML配置中,这意味着要创建一个XML文件,并且要以<beans>元素为根。

<?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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/spring-context.xsd">

    <!-- configuration details go here -->  

</beans>

可以借助Spring Tool Suite生成相应的XML文件。


2.4.2 声明一个简单的<bean>


要在基于XML的Spring配置中声明一个bean,我们要使用spring-beans模式中的另外一个元素:<bean><bean>元素类似于JavaConfig中的@Bean注解。我们可以按照如下的方式声明CompactDisc bean:

<bean id="compactDisc" class="soundsystem.SgtPeppers" />
  • 如果没有指定ID,那么ID将会为soundsystem.SgtPeppers#0。其中#0是计数的方式,用于区分其他相同类型的bean。如果你又声明了一个SgtPeppers,而且没有标识,那么它的ID将会为soundsystem.SgtPeppers#1
  • 稍后将这个bean装配到CDPlayer bean之中的时候,你会用到这个具体的名字ID。减少繁琐为了减少XML中繁琐的配置,只对那些需要按名字引用的bean(比如,你需要将对它的引用注入到另外一个bean中)进行明确地命名。

2.4.3 借助构造器注入初始化bean


XML中声明DI时,有多种配置方案和风格,具体到构造器注入,有两种基本的配置方案可供选择:

  • <constructor-arg>元素
  • 使用Spring 3.0所引入的c-命名空间

构造器注入bean引用

SgtPeppers实现了CompactDisc接口,因此可以被注入到CDPlayer bean中。我们所需要的就是在XML中声明CDPlayer并通过ID引用SgtPeppers:

<?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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/spring-context.xsd">

    <!-- configuration details go here -->  

    <bean id="compactDisc" class="soundsystem.SgtPeppers"></bean>

    <bean id="cdPlayer" class="soundsystem.CDPlayer">
        <constructor-arg ref="compactDisc"></constructor-arg>
    </bean>

</beans>

当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"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:c="http://www.springframework.org/schema/c"
    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">

    <!-- configuration details go here -->  

</beans>

然后可以使用它来声明构造器参数:

<bean id="cdPlayer" class="soundsystem.CDPlayer" c:cd-ref="compactDisc" />

c-命名空间来声明构造器参数,它作为<bean>元素的一个属性,不过这个属性的名字有点诡异。这个属性名组合方式为:

属性名以c:开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后是-ref,这是一个命名的约定,它会告诉Spring,正在装配的是一个bean的引用,这个bean的名字是compactDisc,而不是字面量"compactDisc"

关于c-命名空间,有一件就是它直接引用了构造器参数的名称。引用参数的名称看起来有些怪异,因为这需要在编译代码的时候,将调试标志(debug symbol)保存在类代码中。如果你优化构建过程,将调试标志移除掉,那么这种方式可能就无法正常执行了。

替代的方案是我们使用参数在整个参数列表中的位置信息:

<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_0-ref="compactDisc" />

将参数的名称替换成了“0”,也就是参数的索引。因为在XML中不允许数字作为属性的第一个字符,因此必须要添加一个下画线作为前缀。使用索引来识别构造器参数感觉比使用名字更好一些。即便在构建的时候移除掉了调试标志,参数却会依然保持相同的顺序。如果有多个构造器参数的话,这当然是很有用处的。在这里因为只有一个构造器参数,所以我们还有另外一个方案——根本不用去标示参数:

<bean id="cdPlayer" class="soundsystem.CDPlayer" c:_-ref="compactDisc" />

将字面量注入到构造器中


我们新建一个CompactDisc的实现:

package soundsystem;

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);
    }

}

配置文件为:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:c="http://www.springframework.org/schema/c" 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/beans
        http://www.springframework.org/schema/spring-beans.xsd">

    <!-- configuration details go here -->

    <bean id="compactDisc" class="soundsystem.BlankDisc">
        <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
        <constructor-arg value="The Beatles" />
    </bean>

    <bean id="cdPlayer" class="soundsystem.CDPlayer" c:_-ref="compactDisc" />

</beans>

当然,XML还可以为如下形式:

<bean id="compactDisc" class="soundsystem.BlankDisc">
        <c:_title="Sgt. Pepper's Lonely Hearts Club Band" />
        <c:_artist="The Beatles" />
    </bean>

<bean id="compactDisc" class="soundsystem.BlankDisc">
        <c:_0="Sgt. Pepper's Lonely Hearts Club Band" />
        <c:_1="The Beatles" />
    </bean>

在装配bean引用和字面量值方面,<constructor-arg>c-命名空间的功能是相同的。但是有一种情况是<constructor-arg>能够实现,c-命名空间却无法做到的—装配集合。

装配集合

修改BlankDisc:

package soundsystem;

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);
        }
    }

}

在XML注入时,可以将列表置null<constructor-arg><null></constructor-arg>虽然在构造注入时正常,但是当play()时将会报NullPointerException。因此我们需要如下配置:

<bean id="compactDisc" class="soundsystem.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>的子元素,<value>用于指定列表的每个元素。当然,也可以使用<ref>替代<value>来注入bean。例如:

<ref bean="id"/>

<set><list>基本一致,不过<set>无法保证顺序,且重复的值也会被忽略。不过二者都可以装配List、Set甚至数组。


2.4.4 设置属性


CDPlayer和BlankDisc类完全是通过构造器注入的,没有使用属性的Setter方法。接下来,我们就看一下如何使用Spring XML实现属性注入。
更改CDPlayer:

package soundsystem;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CDPlayer implements MediaPlayer {

    private CompactDisc cd;

    @Autowired
    public void setCompactDisc(CompactDisc cd) {
        this.cd = cd;
    }

    @Override
    public void play() {
        cd.play();
    }

}

该选择构造器注入还是属性注入呢?作为一个通用的规则,我倾向于对强依赖使用构造器注入,而对可选性的依赖使用属性注入。按照这个规则,我们可以说对于BlankDisc来讲,唱片名称、艺术家以及磁道列表是强依赖,因此构造器注入是正确的方案。不过,对于CDPlayer来讲,它对CompactDisc是强依赖还是可选性依赖可能会有些争议。虽然我不太认同,但你可能会觉得即便没有将CompactDisc装入进去,CDPlayer依然还能具备一些有限的功能。

此时XML中无需构造器的参数,在注入时正常,可是一旦play()便会报NullPointerException。所以我们需要修改XML:

<bean id="cdPlayer" class="soundsystem.CDPlayer">
    <property name="compactDisc" ref="compactDisc" />
</bean>

<property>元素为属性的Setter方法所提供的功能与<constructor-arg>元素为构造器所提供的功能是一样的。在本例中,它引用了ID为compactDisc的bean(通过ref属性),并将其注入到compactDisc属性中(通过setCompactDisc()方法)。

Spring也提供了更加简洁的p-空间,作为<property>元素的替代方案。**为了使用p-空间,需要修改XML文件中的声明:

xmlns:p="http://www.springframework.org/schema/p"

然后可以使用p-空间:

<bean id="cdPlayer" class="soundsystem.CDPlayer" p:compactDisc-ref="compactDisc" />

命名属性组成如下:

将字面量注入到属性中

与注入到构造器基本一致。

没有便利的方式使用p-命名空间来指定一个值(或bean引用)的列表。但是我们可以使用Spring util-命名空间来简化BlankDisc bean。
首先在XML中声明使用util-命名空间:

xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util.xsd"

然后我们可以借助util:list声明tracks

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:c="http://www.springframework.org/schema/c" 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/beans
        http://www.springframework.org/schema/spring-beans.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- configuration details go here -->

    <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 Hole</value>
    </util:list>

    <bean id="compactDisc" class="soundsystem.BlankDisc"
        c:_0="Sgt. Pepper's Lonely Hearts Club Band" c:_1="The Beatles"
        c:_2-ref="trackList" />

    <bean id="cdPlayer" class="soundsystem.CDPlayer"
        p:compactDisc-ref="compactDisc" />

</beans>

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

2.5 导入和混合配置


2.5.1 在 JavaConfig 中引用XML配置


我们为了简化每个配置类的复杂性,可以把各个类分开配置:

package soundsystem;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CDConfig {

    @Bean
    public CompactDisc compactDisc() {
        return new SgtPeppers();
    }

}
package soundsystem;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CDPlayerConfig {

    @Bean
    public CDPlayer cdPlayer(CompactDisc compactDisc) {
        return new CDPlayer(compactDisc);
    }

}

为了能够将二者组合起来,一种方法就是在CDPlayerConfig中使用@Import注解:

@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig {
}

不过可以再创建一个更高级别的配置类:

package soundsystem;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({CDConfig.class, CDPlayerConfig.class})
public class SoundSystemConfig {

}

现在,CDPlayer的配置与BlamkDisc的配置分开了,假设我们希望通过XML配置BlankDisc,如下 :

<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 Hole</value>
    </util:list>

<bean id="compactDisc" class="soundsystem.BlankDisc"
        c:_0="Sgt. Pepper's Lonely Hearts Club Band" c:_1="The Beatles"
        c:_2-ref="trackList" />

我们可以通过@ImportResource注解,假设BlankDisc定义在名为cd-config.xml的文件中,该类位于跟路径下,那么可以修改SoundSystemConfig,让它使用@ImportResource注解,如下:

package soundsystem;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;

@Configuration
@Import({CDPlayerConfig.class})
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig {
}

然后运行:

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SoundSystemConfig.class);
    CDPlayer player = context.getBean(CDPlayer.class);
    player.play();
    context.close();
}

两个bean——配置在JavaConfig中的CDPlayer以及配置在XML中BlankDisc——都会被加载到Spring容器之中。因为CDPlayer中带有@Bean注解的方法接受一个CompactDisc作为参数,因此BlankDisc将会装配进来,此时与它是通过XML配置的没有任何关系。


2.5.2 在XML配置中引用JavaConfig


我们可以把XML配置进行拆分,然后用<import resource="cd-config.xml" />进行链接。

在引用JavaConfig时,我们采用<bean id="cd" class="soundsystem.CDConfig" />的形式。

当然,可以向SoundSystemConfig那样生成一个更高层次的配置文件。

不管使用JavaConfig还是使用XML进行装配,我通常都会创建一个根配置(root configuration),也就是这里展现的这样,这个配置会将两个或更多的装配类和/或XML文件组合起来。我也会在根配置中启用组件扫描(通过<context:component-scan>@ComponentScan)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值