本章内容:
1. 声明bean
2. 构造器注入和Setter方法注入
3. 装配bean
4. 控制bean的创建和销毁
在Spring中,对象无需自己查找或创建与其所关联的其他对象。相反,容器负责把需要相互协作的对象引用赋予各个对象。例如,一个订单管理组件需要信用卡认证组件,但它不需要自己创建信用卡认证组件。订单管理组件只需要表明自己两手空空,容器就会主动赋予它一个信用卡认证组件。
创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。
本章目录:
2.1 Spring配置的可选方案
当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:
- 在XML中进行显式配置。
- 在Java中进行显式配置。
- 隐式的bean发现机制和自动装配。
三种装配没有什么特定环境,你可以选择使用XML装配一些bean,使用Spring基于Java的配置(JavaConfig)来装配另一些bean,而将剩余的bean让Spring去自动发现。
但是建议是尽可能地使用自动配置的机制。显式配置越少越好。当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),我推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
2.2 自动化装配bean
Spring从两个角度来实现自动化装配:
- 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
- 自动装配(autowiring):Spring自动满足bean之间的依赖。
组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将你的显式配置降低到最少。
在这里我们创建几个bean,首先,要创建CompactDisc(CD)类,Spring会发现它并将其创建为一个bean。然后,会创建一个CDPlayer(CD播放器)类,让Spring发现它,并将CompactDiscbean注入进来。
2.2.1 创建可被发现的bean
第一步:
CompactDisc接口在Java中定义了CD的概念:
public interface CompactDisc(){
void play();
}
CompactDisc的具体内容并不重要,重要的是你将其定义为一个接口。作为接口,它定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。
第二步:
创建CompactDisc接口的实现类。
带有@Component注解的CompactDisc实现类SgtPeppers:
//该注解证明这个是个bean,需要被创建,待会扫描的就是它
@Component
public class SgtPeppers implements CompactDisc{
private String title="title";
private String artList="artList";
public void play(){
system.out.println("playing "+title+" by "+artList );
}
}
和CompactDisc接口一样,SgtPeppers的具体内容并不重要。你需要注意的就是SgtPeppers类上使用了@Component注解。这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean。没有必要显式配置SgtPeppersbean,因为这个类使用了@Component注解,所以Spring会为你把事情处理妥当。
第三步:
组件扫描默认是不启用的。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。
@ComponentScan注解启用了组件扫描:
/*
@Configuration表明它是一个配置类,待会扫描的就是它所在的包下面的实体类。
@ComponentScan 扫描配置
*/
@Configuration
@ComponentScan
public class CDPlayerConfig{
}
类CDPlayerConfig通过Java代码定义了Spring的装配规则。不过,现在我们只需观察一下CDPlayerConfig类并没有显式地声明任何bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描。
如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为CDPlayerConfig类位于soundsystem包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样的话,就能发现CompactDisc,并且会在Spring中自动为其创建一个bean。
或者通过xml来启用组件扫描。
如果你更倾向于使用XML来启用组件扫描的话,那么可以使用Spring context命名空间的<context:component-scan>
元素。
<context:component-scan base-package="soundsystem">
尽管我们可以通过XML的方案来启用组件扫描,但是在后面的讨论中,我更多的还是会使用基于Java的配置。如果你更喜欢XML的话,<context:component-scan>
元素会有与@ComponentScan注解相对应的属性和子元素。
第四步:
为了测试组件扫描的功能,我们创建一个简单的JUnit测试,它会创建Spring上下文,并判断CompactDisc是不是真的创建出来。
测试组件扫描能够发现CompactDisc:
/*
@RunWith() 表明使用的是spring自带的Junit测试
@@ContextConfiguration(classes=CDPlayerConfig.class)
和spring的配置文件结合测试
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest{
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull(){
cd.play();
}
}
CDPlayerTest使用了Spring的SpringJUnit4ClassRunner,以便在测试开始的时候自动创建Spring的应用上下文。注解@ContextConfiguration会告诉它需要在CDPlayerConfig中加载配置。因为CDPlayerConfig类中包含了@ComponentScan,因此最终的应用上下文中应该包含CompactDiscbean。
输出结果:playing title by artList
表示执行了实体类SgtPeppers的方法,在整个以上流程中我们自己没有任何显式创建实体对象,而是spring通过配置类CDPlayerConfig的扫描找到了带有@Component的SgtPeppers类,然后创建了他的实体对象。
2.2.2 为组件扫描的bean命名
我们在第一章看到了在xml配置文件中每一个组件对象都有一个指定的ID,确保它的唯一性。
注意在注解中,比如在前面的例子中,尽管我们没有明确地为SgtPeppersbean设置ID,但Spring会根据类名为其指定一个ID。具体来讲,这个bean所给定的ID为sgtPeppers,也就是将类名的第一个字母变为小写。
但是我们也可以手动设置ID,案例如下:
@Component("mySgtPeppers")
public class SgtPeppers implements CompactDisc {
......
}
还有一种方式使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named注解来为bean设置ID。
如下:
@Named("mySgtPeppers")
public class SgtPeppers implements CompactDisc {
......
}
注意:二者可以互换,没有太大差别,但是建议使用@Component。
2.2.3 设置组件扫描的基础包
@ComponentScan,现在默认扫描的是该配置类所在的包,但是一般情况下,我们将其是单独放在一个配置包下面,避免污染项目代码,所以如果我们想扫描其他包或者多个包,配置如下:
@Configuration
@ComponentScan("包名称")
public class CDPlayerConfig {
}
或者
@Configuration
@ComponentScan(basePackages={"包名称"})
public class CDPlayerConfig {
}
或者扫描多个包
@Configuration
@ComponentScan(basePackages={"包名称1","包名称2"})
public class CDPlayerConfig {
}
但是问题来了,我们如果重构代码的话,比如修改了包名,难道就要一个个修改这里面的包名称吗,所以经常采用如下配置:
@Configuration
@ComponentScan(basePackageClasses={要扫描的实体类名称1.Class,要扫描的实体类名称2.Class})
public class CDPlayerConfig {
}
注意:
没有必要把所有的实体类都写在上面,如果多个实体类在同一个包中,写一个就ok了,因为它扫描的是该类所在包下的所有实体类。
也可以写实体类的接口。设置可以这样,直接写一个空标记接口,专门用来扫描,这样即使你要删除某个实体类也不会有影响。
2.2.4 通过为bean添加注解实现自动装配
自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
也就是说在某个实体类中有引用对象作为属性。
案例
首先创建一个新的实体接口和实体类
实体接口
package com.javaAction.entity;
public interface MediaPlayer {
public void play();
}
实现接口的实体类
package com.javaAction.entity;
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 CompactDisc getCd() {
return cd;
}
public void setCd(CompactDisc cd) {
this.cd = cd;
}
public void play(){
cd.play();
System.out.println("我是CDPlayer");
}
}
注意@Autowired不仅可以写在构造器上,也可以写在set方法上面。
@Autowired
public void setCd(CompactDisc cd) {
this.cd = cd;
}
@Autowired注解可以用在类的任何方法上。假设CDPlayer类有一个insertDisc()方法,那么@Autowired能够像在setCompactDisc()上那样,发挥完全相同的作用:
@Autowired
public void insertDisc(){
this.cd = cd;
}
不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。
如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowired的required属性设置为false:
@Autowired(required=false)
public void insertDisc(){
this.cd = cd;
}
将required属性设置为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。
注意:
尽量别配置该属性 否则如果真的没有匹配的bean的话,如果使用就会报空指针异常。
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配。
也可以用@Inject替换@Autowired,两个基本上没有太大差别。
2.2.5 验证自动装配
package com.javaAction.test;
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;
import com.javaAction.entity.CDPlayerConfig;
import com.javaAction.entity.CompactDisc;
import com.javaAction.entity.MediaPlayer;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayTest {
@Autowired
private MediaPlayer mediaPlayer;
@Test
public void cdShouldNotNull(){
mediaPlayer.play();
}
}
输出结果:
title=artList
我是CDPlayer
2.3 通过Java代码装配bean
讲完了最常用的自动装配,咱们再说说显式地装配bean。
说一种情况,是不能使用自动装配bean的,比如实体类不是咱们自己写的,而是引用第三方的jar包,想要将第三方带的组件(注意组件也就是实体类)装配到你的应用中,这样没有办法在它的类上添加@Component和@Autowired注解的,这样就不能使用自动化装配方案,必须要采用显式装配的方式。
显式装配有两种方案:使用Java或者xml进行装配(推荐使用java,因为重构性好)。
先说java方案装配bean。
注意这么几点:
(1)首先再javaConfig(就是java装配类)里面不能含有任何业务逻辑代码。
(2)JavaConfig不应该入侵到业务逻辑代码当中去。
(3)尽量将其单独置于一个包下,和业务代码保持分离。
2.3.1 创建配置类
注意看前面的一个配置类:
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
上面中咱们采用扫描并自动装配bean的方式。
此时我们就要开始有所改变了。
@Configuration
public class CDPlayerConfig {
}
@Configuration注解表明这个类是一个配置类,这个不能删除,移除了@ComponentScan注解,此时的CDPlayerConfig类就没有任何作用了。如果你现在运行CDPlayerTest的话,测试会失败,并且会出现BeanCreationException或者NoSuchBeanDefinitionException异常。
2.3.2 声明简单的bean
要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解。
@Configuration
@ComponentScan
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
}
@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册
为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的
逻辑。
默认情况下,bean的ID与带有@Bean注解的方法名是一样的。
也可以指定bean的ID:
@Bean(name="mySgtPeppers")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
该方法体返回了一个新的SgtPeppers实例。
咱们甚至都可以这样做,
在一组CD中随机选择一个CompactDisc来播放:
@Bean(name="mySgtPeppers")
public CompactDisc sgtPeppers(){
int choice=(int) (Math.random()*4);
if(choice==1){
return new SgtPeppers();
}else if(choice==2){
return new SgtPeppers01();
}else{
return new SgtPeppers02();
}
}
2.3.3 借助JavaConfig实现注入
在JavaConfig中装配bean的最简单方式就是引用创建bean的方法。例如,下面就是一种声明CDPlayer的可行方案:
@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,而不是每次都对其进行实际的调用。
可以看到,通过调用方法来引用bean的方式有点令人困惑。其实还有一种理解起来更为简单的方式:
@bean
public CDplayer cdPlayer(CompactDisc compactDisc ){
return new CDPlayer(compactDisc);
}
在这里,cdPlayer()方法请求一个CompactDisc作为参数。当Spring调用cdPlayer()创建CDPlayerbean的时候,它会自动装配一个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 cDplayer=new CDplayer(compactDisc);
cDplayer.setCompactDisc(compactDisc);
return cDplayer;
}
再次强调一遍,带有@Bean注解的方法可以采用任何必要的Java功能
来产生bean实例。构造器和Setter方法只是@Bean方法的两个简单样例。这里所存在的可能性仅仅受到Java语言的限制。
2.4 通过XML装配bean
本节的内容只是用来帮助你维护已有的XML配置,在完成新的Spring工作时,尽量使用自动化配置和JavaConfig。
2.4.1 创建XML配置规范
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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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">
</beans>
2.4.2 声明一个简单的<bean>
要在基于XML的Spring配置中声明一个bean,我们要使用springbeans模式中的另外一个元素:。元素类似于JavaConfig中的@Bean注解。我们可以按照如下的方式声明CompactDiscbean:
<bean class="com.javaAction.entity.SgtPeppers" />
这里声明了一个很简单的bean,创建这个bean的类通过class属性来指
定的,并且要使用全限定的类名。
注意:
如果没有给定ID,bean将会根据全限定类名来进行命名,比如上面的id就是”com.javaAction.entity.SgtPeppers#0”,后面的“#0”是一个计数的形式,用来区分相同类型的其他bean。
但是为了方便后续的引用尽量自己命名。
<bean id="compactDisc" class="com.javaAction.entity.SgtPeppers" />
注意:
在XML装配中就是你不再需要直接负责创建SgtPeppers的实例,在基于JavaConfig的配置中,我们是需要这样做的。当Spring发现这个元素时,它将会调用SgtPeppers的默认构造器来创建bean。在XML配置中,bean的创建显得更加被动,不过,它并没有JavaConfig那样强大,在JavaConfig配置方式中,你可以通过任何可以想象到的方法来创建bean实例。
在这个简单的<bean>
声明中,我们将bean的类型以字符串的形式设置在了class属性中。但是如果我们重命名了类名,那也要很麻烦的来改正XML里面的配置。
2.4.3 借助构造器注入初始化bean
在Spring XML配置中,只有一种声明bean的方式:使用<bean>
元素
并指定class属性。Spring会从这里获取必要的信息来创建bean。
但是,在XML中声明DI(是依赖注入,不是ID)时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可供选择:
<constructor-arg>
元素- 使用Spring 3.0所引入的c-命名空间
两者的区别在很大程度就是是否冗长烦琐。<constructor-arg>
元素比使用c-命名空间会更加冗长,从而导致XML更加难以读懂。但是有些事情<constructor-arg>
可以做到,但是使用c-命名空间却无法实现。
构造器注入bean引用
按照现在的定义,CDPlayerbean有一个接受CompactDisc类型的构造器。现在已经声明了SgtPeppers bean,并且SgtPeppers类实现了CompactDisc接口,所以实际上我们已经有了一个可以注入到CDPlayerbean中的bean。我们所需要做的就是在XML中声明CDPlayer并通过ID引用SgtPeppers:
方案一:
<bean id="compactDisc" class="com.javaAction.entity.SgtPeppers" />
<bean id="cDPlayer" class="com.javaAction.entity.CDPlayer">
<constructor-arg ref="compactDisc"/>
</bean>
当Spring遇到这个<bean>
元素时,它会创建一个CDPlayer实例。<constructor-arg>
元素会告知Spring要将一个ID为compactDisc的bean引用传递到CDPlayer的构造器中。
方案二:
可以使用Spring的c-命名空间。要使用它的话,必须要在XML的顶部声明其模式,如下所示:
在c-命名空间和模式声明之后,我们就可以使用它来声明构造器参数
了,如下所示:
<bean id="cDPlayer" class="com.javaAction.entity.CDPlayer">
<c:cd-ref="compactDisc"/>
</bean>
在这里,我们使用了c-命名空间来声明构造器参数,它作为元素的一个属性,不过这个属性的名字有点诡异。下图描述了这个属性名是如何组合而成的。
属性名以“c:”开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后是“-ref”,这是一个命名的约定,它会告诉Spring,正在装配的是一个bean的引用,这个bean的名字是compactDisc,而不是字面量“compactDisc”。
关于c-命名空间,就是它直接引用了构造器参数的名称。引用参数的名称看起来有些怪异,因为这需要在编译代码的时候,将调试标志(debug symbol)保存在类代码中。如果你优化构建过程,将调试标志移除掉,那么这种方式可能就无法正常执行了。解决办法:
使用参数在整个参数列表中的位置信息:
<bean id="cDPlayer" class="com.javaAction.entity.CDPlayer">
<c:_0-ref="compactDisc"/>
</bean>
这个c-命名空间属性看起来似乎比上一种方法更加怪异。我将参数的名称替换成了“0”,也就是参数的索引。因为在XML中不允许数字作为属性的第一个字符,因此必须要添加一个下画线作为前缀。
当构造器只有一个参数时候,我们还有另外一个方案——根本不用去标示参数:
<bean id="cDPlayer" class="com.javaAction.entity.CDPlayer" c:_-ref="compactDisc"/>
这里没有参数索引或参数名。只有一个下画线,然后就是用“-ref”来表明正在装配的是一个引用。
将字面量注入到构造器中
迄今为止,我们所做的DI通常指的都是类型的装配——也就是将对象的引用装配到依赖于它们的其他对象之中——而有时候,我们需要做的只是用一个字面量值来配置对象。为了阐述这一点,假设你要创建CompactDisc的一个新实现,如下所示:
public class BlankDisc implements CompactDisc{
private String title;
private String artlist;
public BlankDisc(String title, String artlist) {
this.title = title;
this.artlist = artlist;
}
public void play() {
System.out.println("playing " + title + " by " + artlist);
}
}
xml配置如下:
<bean id="compactDisc" class="com.javaAction.entity.SgtPeppers" />
<bean id="compactDisc" class="com.javaAction.entity.BlankDisc">
<constructor-arg value="aa"/>
<constructor-arg value="bb"/>
</bean>
这一次我们没有使用“ref”属性来引用其他的bean,而是使用了value属性,通过该属性表明给定的值要以字面量的形式注入到构造器之中。
采用c-命名空间:
引用构造器参数名字:
<bean id="compactDisc" class="com.javaAction.entity.SgtPeppers" />
<bean id="compactDisc" class="com.javaAction.entity.BlankDisc"
c:_title="aa"
c:-artlist="bb"
/>
通过参数索引装配:
<bean id="compactDisc" class="com.javaAction.entity.SgtPeppers" />
<bean id="compactDisc" class="com.javaAction.entity.BlankDisc"
c:_0="aa"
c:-1="bb"
/>
装配集合:
public class BlankDisc implements CompactDisc{
private String title;
private String artlist;
private List<String> tracks;
public BlankDisc(String title, String artlist, List<String> tracks) {
this.title = title;
this.artlist = artlist;
this.tracks = tracks;
}
public void play() {
System.out.println("playing " + title + " by " + artlist);
for(int i=0;i<tracks.size();i++){
System.out.println("tracks"+i+""+"="+tracks);
}
}
}
xml装配
<bean id="compactDisc" class="com.javaAction.entity.BlankDisc">
<constructor-arg value="aa"/>
<constructor-arg value="bb"/>
<constructor-arg>
<list>
<value>001</value>
<value>002</value>
<value>003</value>
</list>
</constructor-arg>
</bean>
<list>
元素是<constructor-arg>
的子元素,这表明一个包含值的列表将会传递到构造器中。其中,<value>
元素用来指定列表中的每个元素。
同样 list集合里面也可以是对象,那么只要用<ref>
元素替代<value>
。
同样也可以使用set集合。
注意使用c-命名空间的属性无法实现装配集合的功能。
2.4.4 设置属性
到目前为止,CDPlayer和BlankDisc类完全是通过构造器注入的,没有使用属性的Setter方法。接下来,我们就看一下如何使用Spring XML实现属性注入。假设属性注入的CDPlayer如下所示:
@Component
public class CDPlayer implements MediaPlayer{
//在该实体类里面引用其他的实体类对象
private CompactDisc cd;
public CompactDisc getCd() {
return cd;
}
@Autowired
public void setCd(CompactDisc cd) {
this.cd = cd;
}
public void play(){
cd.play();
System.out.println("我是CDPlayer");
}
}
该选择构造器注入还是属性注入呢?作为一个通用的规则,我倾向于对强依赖使用构造器注入,而对可选性的依赖使用属性注入。
XML的配置:
<bean id="cDPlayer" class="com.javaAction.entity.CDPlayer">
<property name="compactDisc" ref="compactDisc">
</bean>
<property>
元素为属性的Setter方法所提供的功能。
它引用了ID为compactDisc的bean(通过ref属性),并将其注入到compactDisc属性中(通过setCompactDisc()方法)。
Spring为<constructor-arg>
元素提供了c-命名空间作为替代方案,与之类似,Spring提供了更加简洁的p-命名空间,作为<property>
元素的替代方案。
我们可以使用p-命名空间,按照以下的方式装配compactDisc属性:
<bean id="cDPlayer" class="com.javaAction.entity.CDPlayer"
p:compactDisc-ref="compactDisc"
/>
p-命名空间中属性所遵循的命名约定与c-命名空间中的属性类似。下图
阐述了p-命名空间属性是如何组成的。
首先,属性的名字使用了“p:”前缀,表明我们所设置的是一个属性。接下来就是要注入的属性名。最后,属性的名称以“-ref”结尾,这会提示Spring要进行装配的是引用,而不是字面量。
将字面量注入到属性中
属性也可以注入字面量,这与构造器参数非常类似。不过,BlankDisc这次完全通过属性注入进行配置,而不是构造器注入。新的BlankDisc类如下所示:
public class BlankDisc implements CompactDisc{
private String title;
private String artlist;
private List<String> tracks;
public void setTitle(String title) {
this.title = title;
}
public void setArtlist(String artlist) {
this.artlist = artlist;
}
public void setTracks(List<String> tracks) {
this.tracks = tracks;
}
public void play() {
System.out.println("playing " + title + " by " + artlist);
for(int i=0;i<tracks.size();i++){
System.out.println("tracks"+i+""+"="+tracks);
}
}
}
现在,它不再强制要求我们装配任何的属性。你可以按照如下的方式创建一个BlankDiscbean,它的所有属性全都是空的:
<bean id="reallyBlankDisc"
class="com.javaAction.entity.BlankDisc"
>
当然,如果在装配bean的时候不设置这些属性,那么在运行期CD播放器将不能正常播放内容。play()方法可能会遇到的输出内容是“Playing null by null”,随之会抛出NullPointerException异常。
<bean id="compactDisc" class="com.javaAction.entity.BlankDisc">
<property name="title" value="aa">
<property name="artlist" value="bb">
<property name="tracks">
<list>
<value>001</value>
<value>002</value>
<value>003</value>
</list>
</property>
</bean>
除了使用<property>
元素的value属性来设置title和artlist,我们还使用了内嵌的<list>
元素来设置tracks属性,这与之前通过<constructor-arg>
装配tracks是完全一样的。
使用p-命名空间的属性来完成该功能:
<bean id="compactDisc" class="com.javaAction.entity.BlankDisc"
p:title="aa"
p:artlist="bb"
>
<property name="tracks">
<list>
<value>001</value>
<value>002</value>
<value>003</value>
</list>
</property>
</bean>
但是我们注意到不能使用p-命名空间来装配集合,可以使用Spring util命名空间中的一些功能来简化BlankDiscbean。
首先,需要在XML中声明util-命名空间及其模式:
可以利用<util:list>
,我们可以将集合属性转移到BlankDisc bean之外,并将其声明到单独的bean之中,如下:
然后:
如下是util-命名空间提供的所有元素:
2.5 导入和混合配置
在典型的Spring应用中,我们可能会同时使用自动化和显式配置。即便你更喜欢通过JavaConfig实现显式配置,但有的时候XML却是最佳的方案。
但是你尽可以将JavaConfig的组件扫描和自动装配和/或XML配置混合在一起。我们至少需要有一点显式配置来启用组件扫描和自动装配。
关于混合配置,第一件需要了解的事情就是在自动装配时,它并不在意要装配的bean来自哪里。自动装配的时候会考虑到Spring容器中所有的bean,不管它是在JavaConfig或XML中声明的还是通过组件扫描获取到的。
2.5.1 在JavaConfig中引用XML配置
我们临时假设CDPlayerConfig已经变得有些笨重,我们想要将其进行拆分。
方案一:
将BlankDisc从CDPlayerConfig拆分出来,定义到它自己的CDConfig类中,如下所示:
compactDisc()方法已经从CDPlayerConfig中移除掉了,我们需要有一种方式将这两个类组合在一起。一种方法就是在CDPlayerConfig中使用@Import注解导入CDConfig:
方案二:
或者不在CDPlayerConfig中使用@Import,而是创建一个更高级别的SoundSystemConfig,在这个类中使用@Import将两个配置类组合在一起:
2.5.2 在XML配置中引用JavaConfig
在XML中,我们可以使用import元素来拆分XML配置。
比如,假设希望将BlankDisc bean拆分到自己的配置文件中,该文件名为cd-config.xml。我们可以在XML配置文件中使用<import>
元素来引用该文件。
<import resource="cd-config.xml"/>
<beans>
</beans>
现在,我们假设不再将BlankDisc配置在XML之中,而是将其配置在JavaConfig中,CDPlayer则继续配置在XML中。问题来了,也就是基于XML的配置该如何引用一个JavaConfig类呢?
注意:<import>
元素只能导入其他的XML配置文件,并没有XML元素能够导入JavaConfig类。
但是,有一个你已经熟知的元素能够用来将Java配置导入到XML配置中:<bean>
元素。为了将JavaConfig类导入到XML配置中,我们可以这样声明bean:
<bean class="com.javaAction.entity.CDPlayerConfig"/>
<bean id="cdPlayer"
class="com.javaAction.entity.CDPlayer"
c:cd-ref="compactDisc"
/>
不管使用JavaConfig还是使用XML进行装配,我通常都会创建一个根配置(root configuration),也就是这里展现的这样,这个配置会将两个或更多的装配类和/或XML文件组合起来。我也会在根配置中启用组件扫描(通过<context:component-scan>
或@ComponentScan)。
2.6 小结
Spring框架的核心是Spring容器。容器负责管理应用中组件的生命周期,它会创建这些组件并保证它们的依赖能够得到满足,这样的话,组件才能完成预定的任务。
Spring中装配bean的三种主要方式:自动化配置、基于Java的显式配置以及基于XML的显式配置。建议尽可能使用自动化配置。