装配bean
spring配置的可选方案
bean装配的三种机制
-
在XML中进行显式配置
-
在java中进行显式配置
-
隐式的bean发现机制和自动装配
尽可能使用自动配置的机制,显式配置越少越好。当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),我推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
自动化装配bean
两个角度实现自动化装配
-
组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean
-
自动装配(autowiring):Spring自动满足bean之间的依赖
创建可被发现的bean
尽管如此,CD为我们阐述DI如何运行提供了一个很好的样例。如果你不将CD插入(注入)到CD播放器中,那么CD播放器其实是没有太大用 处的。所以,可以这样说,CD播放器依赖于CD才能完成它的使命。 为了在Spring中阐述这个例子,让我们首先在Java中建立CD的概念。程序清单2.1展现了CompactDisc,它是定义CD的一个接口:
package soundsystem;
public interface CompactDisc {
void play();
}
CompactDisc的具体内容并不重要,重要的是你将其定义为一个接口。作为接口,它定义了CD播放器对一盘CD所能进行的操作。它将CD 播放器的任意实现与CD本身的耦合降低到了最小的程度。 我们还需要一个CompactDisc的实现,实际上,我们可以有CompactDisc接口的多个实现。在本例中,我们首先会创建其中的一个实现, 也就是程序清单2.2所示的SgtPeppers类。
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";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
和CompactDisc接口一样,SgtPeppers的具体内容并不重要。你需要注意的就是SgtPeppers类上使用了@Component注解。这个简单 的注解表明该类会作为组件类,并告知Spring要为这个类创建bean。没有必要显式配置SgtPeppersbean,因为这个类使用 了@Component注解,所以Spring会为你把事情处理妥当。 不过,组件扫描默认是不启用的。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。程序清 单2.3的配置类展现了完成这项任务的最简洁配置。
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
类CDPlayerConfig通过Java代码定义了Spring的装配规则。在2.3节中,我们还会更为详细地介绍基于Java的Spring配置。不过,现在我们 只需观察一下CDPlayerConfig类并没有显式地声明任何bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组 件扫描。 如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为CDPlayerConfig类位于soundsystem包中,因此Spring 将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样的话,就能发现CompactDisc,并且会在Spring中自动为 其创建一个bean。 如果你更倾向于使用XML来启用组件扫描的话,那么可以使用Spring context命名空间的元素。程序清 单2.4展示了启用组件扫描的最简洁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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
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/context/spring-context.xsd">
<context:component-scan base-package="soundsystem" />
</beans>
尽管我们可以通过XML的方案来启用组件扫描,但是在后面的讨论中,我更多的还是会使用基于Java的配置。如果你更喜欢XML的 话,元素会有与@ComponentScan注解相对应的属性和子元素。 可能有点让人难以置信,我们只创建了两个类,就能对功能进行一番尝试了。为了测试组件扫描的功能,我们创建一个简单的JUnit测试,它会 创建Spring上下文,并判断CompactDisc是不是真的创建出来了。程序清单2.5中的CDPlayerTest就是用来完成这项任务的。
package soundsystem;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
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);
}
}
CDPlayerTest使用了Spring的SpringJUnit4ClassRunner,以便在测试开始的时候自动创建Spring的应用上下文。
注解@ContextConfiguration会告诉它需要在CDPlayerConfig中加载配置。因为CDPlayerConfig类中包含了@ComponentScan, 因此最终的应用上下文中应该包含CompactDiscbean。
为了证明这一点,在测试代码中有一个CompactDisc类型的属性,并且这个属性带有@Autowired注解,以便于将CompactDiscbean注入 到测试代码之中(稍后,我会讨论@Autowired)。最后,会有一个简单的测试方法断言cd属性不为null。如果它不为null的话,就意味着 Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码之中。
这个代码应该能够通过测试,并以测试成功的颜色显示(在你的测试运行器中,或许会希望出现绿色)。你第一个简单的组件扫描练习就成功 了!尽管我们只用它创建了一个bean,但同样是这么少的配置能够用来发现和创建任意数量的bean。在soundsystem包及其子包中,所有带 有@Component注解的类都会创建为bean。只添加一行@ComponentScan注解就能自动创建无数个bean,这种权衡还是很划算的。
为组件扫描的bean命名
前面的例子中没有给SgtPeppersbean设置ID,但spring会根据类名为其指定一个id。
如果想为这个bean设置不同的id,需要将期望的id传递给@Component注解。
package soundsystem;
import org.springframework.stereotype.Component;
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
还有另外一种为bean命名的方式,这种方式不使用@Component注解,而是使用Java依赖注入规范(Java Dependency Injection)中所提供 的@Named注解来为bean设置ID:
package soundsystem;
import org.springframework.stereotype.Component;
@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
Spring支持将@Named作为@Component注解的替代方案。两者之间有一些细微的差异,但是在大多数场景中,它们是可以互相替换的。
设置组件扫描的基础包
我们没有为@ComponentScan设置任何属性。这意味着,按照默认规则,它会以配置类所在的包作为基础包(base package) 来扫描组件。但是,如果你想扫描不同的包,你所需要做的就是在@ComponentScan的value属性中指明包的名称:
@Configuration
@ComponentScan("soundsystem")
public class CDPlayerConfig {
}
更加清晰地表明你所设置的是基础包,那么你可以通过basePackages属性进行配置
@Configuration
@ComponentScan(basePackages = "soundsystem")
public class CDPlayerConfig {
}
basePackages属性使用的是复数形式,可以设置多个基础包,只需要将basePackages属性设置为要扫描包的一个数组即可
@Configuration
@ComponentScan(basePackages = {"soundsystem", "video"})
public class CDPlayerConfig {
}
在上面的例子中,所设置的基础包是以String类型表示的。我认为这是可以的,但这种方法是类型不安全(not type-safe)的。如果重构代码的话,那么所指定的基础包可能就会出现错误了。
除了将包设置为简单的String类型之外,@ComponentScan还提供了另外一种方法,那就是将其指定为包中所包含的类或接口:
@Configuration
@ComponentScan(basePackageClasses ={CDPlayer.class,DVDPlayer.class} )
public class CDPlayerConfig {
}
可以看到,basePackages属性被替换成了basePackageClasses。同时,我们不是再使用String类型的名称来指定包, 为basePackageClasses属性所设置的数组中包含了类。这些类所在的包将会作为组件扫描的基础包。
通过bean添加注解实现自动装配
自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其 他bean。
CDPlayer类的构造器上添加了@Autowired注解,这表明当Spring创建CDPlayerbean的时候,会通过 这个构造器来进行实例化并且会传入一个可设置给CompactDisc类型的bean。
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;
}
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()上那样,发挥完全相同的作用:
@Autowired
public void insertDisc(CompactDisc cd){
this.cd = cd;
}
不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么 这个bean将会被装配进来。 如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowired的required属 性设置为false:
@Autowired(required=false)
public void insertDisc(CompactDisc cd){
this.cd = cd;
}
将required属性设置为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。 但是,把required属性设置为false时,你需要谨慎对待。如果在你的代码中没有进行null检查的话,这个处于未装配状态的属性有可能会 出现NullPointerException。
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配.
@Autowired是Spring特有的注解。如果你不愿意在代码中到处使用Spring的特定注解来完成自动装配任务的话,那么你可以考虑将其替换 为@Inject:
package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Named
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Inject
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
}
@Inject注解来源于Java依赖注入规范,该规范同时还为我们定义了@Named注解。在自动装配中,Spring同时支 持@Inject和@Autowired。尽管@Inject和@Autowired之间有着一些细微的差别,但是在大多数场景下,它们都是可以互相替换的。
验证自动装配
在CDPlayer的构造器中添加了@Autowired注解,Spring将把一个可分配给CompactDisc类型的bean自动注入进来。为了 验证这一点,让我们修改一下CDPlayerTest,使其能够借助CDPlayer bean播放CD:
package soundsystem;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
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 {
@Rule
public final StandardOutputStreamLog log = new StandardOutputStreamLog();
@Autowired
private MediaPlayer player;
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
}
@Test
public void play() {
player.play();
assertEquals(
"Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\n",
log.getLog());
}
}
除了注入CompactDisc,我们还将CDPlayerbean注入到测试代码的player成员变量之中(它是更为通用的MediaPlayer类 型)。在play()测试方法中,我们可以调用CDPlayer的play()方法,并断言它的行为与你的预期一致。
通过java代码装配bean
创建配置类
创建JavaConfig类的关键在于为其添加@Configuration注解,此注解表示这个类是一个配置类
声明简单的bean
在JavaConfig中声明bean,编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解
@Bean
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
@Bean注解会告诉spring这个方法将会返回一个对象,该对象要注册为spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。
默认情况下,bean的id与带有@Bean注解的方法名是一样的,也可重命名该方法,也可以通过name属性指定一个不同的名字
@Bean(name="lonelyHeartsCluBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
方法体返回了一个新的SgtPeppers实例,这里是使用java来进行描述的,因此可以发挥java提供的所有功能,只要最终生成一个CompactDisc实例即可
@Bean
public CompactDisc randomBeatlesCD(){
int choice = (int)Math.floor(Math.random()*4);
if(choice == 0){
return new SgtPeppers();
}else if(choice == 1){
return new WhiteAlbum();
}else if(choice == 2){
return new HardDaysNight();
}else{
return new Revolver();
}
}
借助JavaConfig实现注入
生命CDPlayer bean,它依赖于CompactDisc
@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}
CompactDisc是通过调用sgtPeppers()得到的,因为sgtPeppers()方法上添加了@Bean注解,Spring将会拦截所有对他的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用。
比如说,假设引入了一个其他的CDPlayer bean,他和之前的那个bean完全一样:
@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}
@Bean
public CDPlayer anotherCDPlayer(){
return new CDPlayer(sgtPeppers());
}
如果对sgtPeppers()的调用就像其他的Java方法调用一样的话,那么每个CDPlayer实例都会有一个自己特有的SgtPeppers实例。
默认情况下,Spring中的bean都是单例的,没有必要为第二个CDPlayer bean创建完全相同的SgtPeppers实例。所以,Spring会拦截对sgtPeppers()的调用并确保返回的是Spring所创建的bean,也就是Spring本身在调用sgtPeppers()时所创建的CompactDisc bean。因此,两个CDPlayer bean会得到相同的SgtPeppers实例。
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
这里使用CDPlayer的构造器实现了DI功能,但是我们完全可以采用其他风格的DI配置。
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
CDPlayer cdPlayer = new CDPlayer(compactDisc);
cdPlayer.setCompactDisc(compactDisc);
return cdPlayer;
}
带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例。构造器和Setter只是@Bean方法的两个简单样例。
通过XML装配bean
创建XML配置规范
使用JavaConfig的时候,这意味要创建一个带有@Configuration,而在XML配置中,要创建一个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"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- configuration details go here -->
</beans>
使用xml时,需要在配置文件的顶部声明多个xml模式(XSD)文件,这些文件定义了配置Spring的XML元素。
借助Spring Tool Suite 配置文件创建和管理 File>New>Spring Bean Configuration File创建Spring XML配置文件,并且可以选择可用的配置命名空间。
装配bean的最基本的XML元素包含在spring-beans模式中,定义为根命名空间。是该模式中的一个元素,是所有Spring配置文件的根元素。
声明一个简单的
使用spring-beans模式的另一个元素,类似JavaConfig中的@Bean注解
<bean class="soundsystem.SgtPeppers" />
这个bean的类通过class属性来制定的,并且要用全限定的类名,bean的ID将是"soundsystem.SgtPeppers#0","#0"是一个计数形式,用来区分相同类型的其他bean
<bean id="compactDisc" class="soundsystem.SgtPeppers" />
虽然自动化命名很方便,但如果要引用他,自动产生的名字就没有多大的用处了。通常借助id属性来为bean设置一个自己选择的名字
借助构造器注入初始化bean
Spring XML配置中只有一种声明bean的方式,使用元素并指定class属性。但是,在XML中声明DI时,具体到构造器注入,有两种基本配置方案:
- 元素
- 使用Spring 3.0所引入的c-命名空间
两者区别就是是否冗长繁琐。元素比使用c-命名空间会更加冗长,导致XML更加难以读懂。另外,有些事情可以做到,而c-命名空间无法实现
构造器注入bean引用
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>
Spring遇到元素时,会创建CDPlayer实例。元素会告知Spring要将一个id为compactDisc的bean引用传递到CDPlayer构造器中。
要使用c-命名空间,必须在XML的顶部声明其模式
<bean id="cdPlayer" class="soundsystem.CDPlayer"
c:cd-ref="compactDisc" />
属性名以“c:”开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后是“-ref”,这是一个命名的约定,它会告诉 Spring,正在装配的是一个bean的引用,这个bean的名字是compactDisc,而不是字面量“compactDisc”。
引用参数的名称看起来有些怪异, 因为这需要在编译代码的时候,将调试标志(debug symbol)保存在类代码中。如果你优化构建过程,将调试标志移除掉,那么这种方式可能 就无法正常执行了。
<bean id="cdPlayer" class="soundsystem.CDPlayer"
c:_0-ref="compactDisc" />
将参数的名称替换成了“0”,也就是参数的索引。因为在XML中不允许数字作为属性 的第一个字符,因此必须要添加一个下画线作为前缀。
使用索引来识别构造器参数感觉比使用名字更好一些。即便在构建的时候移除掉了调试标志,参数却会依然保持相同的顺序。如果有多个构造 器参数的话,这当然是很有用处的。在这里因为只有一个构造器参数,所以我们还有另外一个方案——根本不用去标示参数:
<bean id="cdPlayer" class="soundsystem.CDPlayer"
c:_-ref="compactDisc" />
这是最为奇特的一个c-命名空间属性,这里没有参数索引或参数名。只有一个下画线,然后就是用“-ref”来表明正在装配的是一 个引用。
将字面量注入到构造器中
创建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;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
}
}
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
</bean>
使用元素进行构造器参数的注入。但是这一次我们没有使用“ref”属性来引用其他的bean,而是使用 了value属性,通过该属性表明给定的值要以字面量的形式注入到构造器之中。
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_title="Sgt. Pepper's Lonely Hearts Club Band"
c:_artist="The Beatles" />
可以看到,装配字面量与装配引用的区别在于属性名中去掉了“-ref”后缀。与之类似,我们也可以通过参数索引装配相同的字面量值,如下所 示:
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_0="Sgt. Pepper's Lonely Hearts Club Band"
c:_1="The Beatles" />
XML不允许某个元素的多个属性具有相同的名字。因此,如果有两个或更多的构造器参数的话,我们不能简单地使用下画线进行标示。但是如 果只有一个构造器参数的话,我们就可以这样做了。为了完整地展现该功能,假设BlankDisc只有一个构造器参数,这个参数接受唱片的名 称。在这种情况下,我们可以在Spring中这样声明它:
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_="Sgt. Pepper's Lonely Hearts Club Band" />
装配集合
package soundsystem.collections;
import java.util.List;
import soundsystem.CompactDisc;
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;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
for (String track : tracks) {
System.out.println("-Track: " + track);
}
}
}
<bean id="compactDisc" class="soundsystem.collections.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
<constructor-arg><null/></constructor-arg>
</bean>
元素所做的事情与你的期望是一样的:将null传递给构造器。这并不是解决问题的好办法,但在注入期它能正常执行。当调 用play()方法时,你会遇到NullPointerException异常,因此这并不是理想的方案。
更好的解决方法是提供一个磁道名称的列表。要达到这一点,我们可以有多个可选方案。首先,可以使用元素将其声明为一个列表:
<bean id="compactDisc" class="soundsystem.collections.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>
<value>She's Leaving Home</value>
<value>Being for the Benefit of Mr. Kite!</value>
<value>Within You Without You</value>
<value>When I'm Sixty-Four</value>
<value>Lovely Rita</value>
<value>Good Morning Good Morning</value>
<value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
<value>A Day in the Life</value>
</list>
</constructor-arg>
</bean>
元素是的子元素,这表明一个包含值的列表将会传递到构造器中。其中,元素用来指定列表 中的每个元素。
我们也可以使用元素替代,实现bean引用列表的装配。例如,假设你有一个Discography类,它的构造器如下 所示:
当构造器参数的类型是java.util.List时,使用元素是合情合理的。尽管如此,我们也可以按照同样的方式使用元素:
和元素的区别不大,其中最重要的不同在于当Spring创建要装配的集合时,所创建的是java.util.Set还 是java.util.List。如果是Set的话,所有重复的值都会被忽略掉,存放顺序也不会得以保证。不过无论在哪种情况 下,或都可以用来装配List、Set甚至数组。
在装配集合方面,比c-命名空间的属性更有优势。
设置属性
package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import soundsystem.CompactDisc;
import soundsystem.MediaPlayer;
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
@Autowired
public void setCompactDisc(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
public void play() {
compactDisc.play();
}
}
该选择构造器注入还是属性注入呢?作为一个通用的规则,我倾向于对强依赖使用构造器注入,而对可选性的依赖使用属性注入。
我们可以说对于BlankDisc来讲,唱片名称、艺术家以及磁道列表是强依赖,因此构造器注入是正确的方案。不过,对于CDPlayer来 讲,它对CompactDisc是强依赖还是可选性依赖可能会有些争议。
CDPlayer没有任何的构造器(除了隐含的默认构造器),它也没有任何的强依赖。因此,你可以采用如下的方式将其声明为Spring bean:
<bean id="cdPlayer"
class="soundsystem.CDPlayer"/>
Spring在创建bean的时候不会有任何的问题,但是CDPlayerTest会因为出现NullPointerException而导致测试失败,因为我们并没有 注入CDPlayer的compactDisc属性。
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<property name="compactDisc" ref="compactDisc" />
</bean>
元素为属性的Setter方法所提供的功能与元素为构造器所提供的功能是一样的。在本例中,它引用了ID 为compactDisc的bean(通过ref属性),并将其注入到compactDisc属性中(通过setCompactDisc()方法)
Spring提供了更加简洁的p-命名空间,作为元素的替代方案。为了启用p-命名空间,必须要在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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
<bean id="cdPlayer"
class="soundsystem.properties.CDPlayer"
p:compactDisc-ref="compactDisc" />
属性的名字使用了“p:”前缀,表明我们所设置的是一个属性。接下来就是要注入的属性名。最后,属性的名称以“-ref”结尾,这会提示 Spring要进行装配的是引用,而不是字面量。
将字面量注入到属性中
package soundsystem.properties;
import java.util.List;
import soundsystem.CompactDisc;
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;
}
public void play() {
System.out.println("Playing " + title + " by " + artist);
for (String track : tracks) {
System.out.println("-Track: " + track);
}
}
}
<bean id="compactDisc" class="soundsystem.BlankDisc" />
如果在装配bean的时候不设置这些属性,那么在运行期CD播放器将不能正常播放内容。play()方法可能会遇到的输出内容是“Playing null by null”,随之会抛出NullPointerException异常,这是因为我们没有指定任何的磁道。
<bean id="compactDisc"
class="soundsystem.properties.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 Hole</value>
<value>She's Leaving Home</value>
<value>Being for the Benefit of Mr. Kite!</value>
<value>Within You Without You</value>
<value>When I'm Sixty-Four</value>
<value>Lovely Rita</value>
<value>Good Morning Good Morning</value>
<value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
<value>A Day in the Life</value>
</list>
</property>
</bean>
除了使用元素的value属性来设置title和artist,我们还使用了内嵌的元素来设置tracks属性,这与之前通过装配tracks是完全一样的。
<bean id="compactDisc"
class="soundsystem.properties.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 Hole</value>
<value>She's Leaving Home</value>
<value>Being for the Benefit of Mr. Kite!</value>
<value>Within You Without You</value>
<value>When I'm Sixty-Four</value>
<value>Lovely Rita</value>
<value>Good Morning Good Morning</value>
<value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
<value>A Day in the Life</value>
</list>
</property>
</bean>
与c-命名空间一样,装配bean引用与装配字面量的唯一区别在于是否带有“-ref”后缀。如果没有“-ref”后缀的话,所装配的就是字面量。
但需要注意的是,我们不能使用p-命名空间来装配集合,没有便利的方式使用p-命名空间来指定一个值(或bean引用)的列表。但是,我们可 以使用Spring util-命名空间中的一些功能来简化BlankDiscbean。
需要在XML中声明util-命名空间及其模式:
<?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:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
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-命名空间所提供的功能之一就是元素,它会创建一个列表的bean。借助,我们可以将磁道列表转移 到BlankDisc bean之外,并将其声明到单独的bean之中,如下所示:
<bean id="compactDisc"
class="soundsystem.properties.BlankDisc"
p:title="Sgt. Pepper's Lonely Hearts Club Band"
p:artist="The Beatles"
p:tracks-ref="trackList" />
<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>
<value>She's Leaving Home</value>
<value>Being for the Benefit of Mr. Kite!</value>
<value>Within You Without You</value>
<value>When I'm Sixty-Four</value>
<value>Lovely Rita</value>
<value>Good Morning Good Morning</value>
<value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
<value>A Day in the Life</value>
</util:list>
< util:list >只是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,其中包含值或引用 |
导入和混合配置
在JavaConfig中引用XML配置
BlankDisc从CDPlayerConfig拆分出来,定义到它自己的CDConfig类中,如下所示:
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();
}
}
compactDisc()方法已经从CDPlayerConfig中移除掉了,我们需要有一种方式将这两个类组合在一起。一种方法就是 在CDPlayerConfig中使用@Import注解导入CDConfig:
package soundsystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig {
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
}
或者采用一个更好的办法,也就是不在CDPlayerConfig中使用@Import,而是创建一个更高级别的SoundSystemConfig,在这个类中 使用@Import将两个配置类组合在一起:
package soundsystem;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
@Configuration
@Import({CDPlayerConfig.class,CDConfig.class})
public class SoundSystemConfig {
}
不管采用哪种方式,我们都将CDPlayer的配置与BlankDisc的配置分开了。现在,我们假设(基于某些原因)希望通过XML来配 置BlankDisc,如下所示:
<bean id="compactDisc"
class="soundsystem.BlankDisc"
c:_0="Sgt. Pepper's Lonely Hearts Club Band"
c:_1="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>
<!-- ...other tracks omitted for brevity... -->
</list>
</constructor-arg>
</bean>
现在BlankDisc配置在了XML之中,用@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 {
}
配置在JavaConfig中的CDPlayer以及配置在XML中BlankDisc——都会被加载到Spring容器之中。因为CDPlayer中带 有@Bean注解的方法接受一个CompactDisc作为参数,因此BlankDisc将会装配进来,此时与它是通过XML配置的没有任何关系。
在XML配置中引用JavaConfig
将BlankDisc bean拆分到自己的配置文件中,该文件名为cd-config.xml,这与我们之前使用@ImportResource是一样的。 我们可以在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"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="soundsystem.CDConfig" />
<bean id="cdPlayer"
class="soundsystem.CDPlayer"
c:cd-ref="compactDisc" />
</beans>
不再将BlankDisc配置在XML之中,而是将其配置在JavaConfig中,CDPlayer则继续配置在XML中。
元素只能导入其他的XML配置文件,并没有XML元素能够导入JavaConfig类。
有一个你已经熟知的元素能够用来将Java配置导入到XML配置中:元素。为了将JavaConfig类导入到XML配置中,我们可以这 样声明bean:
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="soundsystem.CDConfig" />
<bean id="cdPlayer"
class="soundsystem.CDPlayer"
c:cd-ref="compactDisc" />
</beans>
采用这样的方式,两种配置——其中一个使用XML描述,另一个使用Java描述——被组合在了一起。类似地,你可能还希望创建一个更高层次 的配置文件,这个文件不声明任何的bean,只是负责将两个或更多的配置组合起来。
以将CDConfig bean从之前的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"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="soundsystem.CDConfig" />
< import resource="cdplayer-config.xml" />
</beans>
不管使用JavaConfig还是使用XML进行装配,我通常都会创建一个根配置(root configuration),也就是这里展现的这样,这个配置会将两个 或更多的装配类和/或XML文件组合起来。我也会在根配置中启用组件扫描(通过<context:component-scan >或@ComponentScan)。