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会抛出一个异常。为了避免异常的出现,你可以将@Autowired
的required
属性设置为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
注解的,因此就不能使用自动化装配的方案了。在这种情况下,你必须要采用显式装配的方式。在进行显式配置的时候,有两种可选方案:Java和XML。
在进行显式配置时,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
)。