创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。Spring提供了三种主要装配机制。
Spring的配置 风格是可以互相搭配的,所以你可以选择使用XML装配一些bean,使用Spring基于Java的配置(JavaConfig) 来装配另一些bean,而将剩余的bean让Spring去自动发现。即便如此,我的建议是尽可能地使用自动配置的机制。显式配置越少越好。当你必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),我推荐使用类型安全并且比XML更加强大的JavaConfig。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
Spring从两个角度来实现自动化装配:
组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将你的显式配置降低到最少。
例:创建可被发现的bean
(1)CompactDisc 接口
package soundsystem;
public interface CompactDisc {
void play();
}
(2)带有@Component注解的CompactDisc实现类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);
}
}
SgtPeppers类上使用了@Component注解。这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean。没有必要显式配置SgtPeppersbean, 因为这个类使用了@Component注解,所以Spring会为你把事情处理妥当。
不过,组件扫描默认是不启用的。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。
(3)@ComponentScan注解启用了组件扫描
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
CDPlayerConfig类并没有显式地声明任何bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描。
如果没有其他配置的话,@ComponentScan默认 会扫描与配置类相同的包。因为CDPlayerConf ig类位于soundsystem包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样的话,就能发现CompactDisc,并且会在Spring中自动为其创建一个bean。如果你更倾向于使用XML来启用组件扫描的话,那么可以使用Spring context 命名空间的
(4)通过XML启用组件扫描
(5)测试组件扫描能够发现CompactDisc
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();
}
}
注解@ContextConfiguration会告诉它需要在CDPlayerConfig中加载配置。因为CDPlayerConfig类中包含 了@ComponentScan,因此最终的应用上下文中应该包含CompactDi scbean。
例:为组件扫描的bean命名
Spring应用上下文中所有的bean都会给定一个ID。 在前面的例子中,尽管我们没有明确地为SgtPeppers bean设置ID,但Spring会根据类名为其指定一个ID。具体来讲,这个bean所给定的ID为sgtPeppers,也就是将类名的第一个字母变为小写。
如果想为这个bean设置不同的ID,你所要做的就是将期望的ID作为值传递给@Component注解。比如说,如果想将这个bean标识为lonelyHeartsClub,那么你需要将SgtPeppers类的@Component注解配置为如下所示:
@Component(“lonelyHeartsClub”)
public class SgtPeppes implements CompactDisc {
...
}
还有另外一种为bean命名的方式,这种方式不使用@Component注解,而是使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named注解来为bean设置ID: .
例:
@Named(“lonelyHeartsClub”)
public class SgtPeppes implements CompactDisc {
...
}
Spring支持将@Named作为@Component注解的替代方案。两者之间有一些细微的差异,但是在大多数场景中,它们是可以互相替换的。
例:设置组件扫描的基本包
如果@ComponentScan没设置属性则它会默认以配置类所在的包作为基础包(base package)为了指定不同的基础包,你所需要做的就是在@ComponentScan的value属性中指明包的名称:
例:
@Configuration
@ComponetScan(“soundsystem”)
public class CDPlayerConfig {
}
可通过basePackages更加清晰表达设置的为基础包,当然可指定多个基础包。
例:
@Configuration
@ComponetScan(basePackages={“soundsystem”, “video”})
public class CDPlayerConfig {
}
在上面的例子中,所设置的基础包是以str ing类型表示的。这是可以的,但这种方法是类型不安全(not type-safe)的。如果你重构代码的话,那么所指定的基础包可能就会出现错误了。
除了将包设置为简单的String类型之外,@ComponentScan还提供了另外一种方法,那就是将其指定为包中所包含的类或接口:
例:
@Configuration
@ComponetScan(basePackages={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {
}
尽管在上例中,用basePackageClasses 设置的是组件类,但是你可以考虑在包中创建一个用来进行扫描的空标记接口(marker interface ),通过标记接口的方式,你依然能够保持对重构友好的接口引用,但是可以避免引用任何实际的应用程序代码。
例:通过为bean添加注解实现自动装配
简单来说,自动装配就是i让Spring自动满足bean依赖的一一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowi red注解。
例:
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不仅可以用在构造函数上,还可以用在属性和方法中。如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowi red的required属性设置为false:
例:
@Autowired(required = false)
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
将required属性设置为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,Spring将 会让这个bean处于未装配的状态。但是,把required属性设置为false时,你需要谨慎对待。如果在你的代码中没有进行null检查的话,这个处于未装配状态的属性有可能会出现NullPointerException.
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配。
@Autowi red是Spring特有的注解。如果你不愿意在代码中到处使用Spring的特定注解来完成自动装配任务的话,那么你可以考虑将其替换为@Inject: .
例:
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
@Inject注解来源于Java依赖注入规范,该规范同时还为我们定义了@Named注解。在自动装配中,Spring同时支持@Inject和@Autowired.尽管@Inject和@Autowired之间有着一些细微的差别, 但是在大多数场景下,它们都是可以互相替换的。
例:验证自动转配
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());
}
}
大多数场景都推荐自动化配置,但有时候自动化配置不能使用,比如在将第三方库中的组件装配到自己的应用中的时候。在这种情况下,你必须要采用显式装配的方式。在进行显式配置的时候,有两种可选方案: Java和XML。
在进行显式配置时,JavaConfig是 更好的方案,因为它更为强大、类型安全并且对重构友好。因为它就是Java代码,就像应用程序中的其他Java代码一样。同时,JavaConfig 与其他的Java代码又有所区别,在概念上,它与应用程序中的业务逻辑和领域代码是不同的。尽管它与其他的组件一样都使用相同的语言进行表述,但JavaConfig是配置代码。这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。尽管不是必须的,但通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了。
例:创建配置类
package soundsystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc compactDisc() {
return new SgtPeppers();
}
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
}
创建JavaConfig类的关键在于为其添加@Configuration注解,@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。
@Bean注解会告诉Spring这个方法将会返回一个对象, 该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。默认情况下,bean的ID与带有@Bean注解的方法名是一样的。如果要重命名的话可以用@Bean(name=”beanName”)。
默认情况下,Spring中的bean都是单例的。如果我们有第二个CDPlayer也调用了相同的SgtPeppers bean,我们并没有必要为第二个CDPlayer bean创建完全相同的SgtPeppers实例。所以,Spring会拦截对sgtPeppers ()的调用并确保返回的是Spring所创建的bean,也就是Spring本身在调用sgtPeppers ()时所创建的CompactDiscbean。因此,两个CDPlayer bean会得到相同的SgtPeppers实例。
可以看到,通过调用方法来引用bean的方式有点令人困惑。其实还有一种理解起来更为简单的方式:
例:
@Bean
public CDP1ayer cdP1ayer (CompactDisc compactDisc) {
return new CDPlayer ( compactDisc) ;
}
在这里,cdPlayer ()方法请求一个CompactDisc作为参数。当Spring调用cdPlayer ()创建CDPlayerbean的时候,它会自动装配一个CompactDisc到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer ()方法也能够将CompactDisc注入到CDPlayer的构造器中,而且不用明确引用CompactDisc的@Bean方法。
通过这种方式引用其他的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置类之中。在这里甚至没有要求CompactDisc必须要在JavaConfig中声明,实际上它可以通过组件扫描功能自动发现或者通过XML来进行配置。你可以将配置分散到多个配置类、XML文件以及自动扫描和装配bean之中,只要功能完整健全即可。不管CompactDi sc是采用什么方式创建出来的,Spring都 会将其传入到配置方法中,并用来创建CDPlayer bean.
另外,需要提醒的是,我们在这里使用CDPlayer的构造器实现了DI功能,但是我们完全可以采用其他风格的DI配置。比如说,如果你想通过
Setter方法注入CompactDisc的话,那么代码看起来应该是这样的:
@Bean
public CDPlayer cdPlayer (CompactDisc compactDisc} {
CDPlayer cdPlayer = new CDP1ayer (compactDisc) ;
cdPlayer。setCompactDisc lcompactDisc) ;
return cdPlayer;
}
(1)创建XML配置规范
最为简单的Spring XML配置如下所示:
beans>
(2)声明一个简单的
要在基于XML的Spring配置中声明一个bean,我们要使用spring-beans模式中的另外- -个元素:
这里声明了一个很简单的bean,创建这个bean的类通过class属性来指定的,并且要使用全限定的类名。
因为没有明确给定ID,所以这个bean将会根据全限定类名来进行命名。在本例中,bean的D将 会是"soundsystem. SgtPeppers#0"。其中,“#0”是一个计数的形式,用来区分相同类型的其他bean。 如果你声明了另外一个SgtPeppers,并且没有明确进行标识,那么它自动得到的ID将会是“soundsystem. SgtPeppers#1"。
尽管自动化的bean命名方式非常方便,但如果你要稍后引用它的话,那自动产生的名字就没有多大的用处了。因此,通常来讲更好的办法是借助id属性,为每个bean设置一个你自 己选择的名字:
减少繁琐:为了减少XML中繁琐的配置,只对那些需要按名字引用的bean (比如,你需要将对它的引用注入到另外一个bean中)进行明确地命名。
这个简 单bean声明的一些特征:
第一件需要注意的事情就是你不再需要直接负责创建sgtPeppers的实例,在基于JavaConfig的配置中,我们是需要这样做的。当Spring发现这个
另外一个需要注意到的事情就是,在这个简单的
(3)借助构造器注入初始化bean
在Spring XML配置中,只有- -种声明bean的方式:使用
但是,在XML中声明DI时,会有多种可选的配置方案和风格。具体到构造器注入,有两种基本的配置方案可供选择:
两者的区别在很大程度就是是否冗长烦琐。可以看到,
(a)构造器注入bean引用
例如在XML中声明CDPlayer并通过ID引用SgtPeppers:
当Spring遇到这个
作为替代的方案,你也可以使用Spring的c-命名空间。c-命 名空间是在Spring 3.0中引入的,它是在XML中更为简洁地描述构造器参数的方式。要使用它的话,必须要在XML的顶部声明其模式,如下所示:
...
< /beans>
在c-命名空间和模式声明之后,我们就可以使用它来声明构造器参数了,如下所示:<
在这里,我们使用了c-命名空间来声明构造器参数,它作为
属性名以“c:"开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后是“-ref", 这是一个命名的约定,它会告诉Spring,正在装配的是一个bean的引用, 这个bean的名字是compactDisc,而不是字面量“compactDisc"。
(b)将字面量注入到构造器中
例:
.
我们再次使用
如果要使用c-命名空间的话,这个例子又该是什么样子呢?第一种方案是引用构造器参数的名字: .
可以看到,装配字面量与装配引用的区别在于属性名中去掉了“-ref"后缀。与之类似,我们也可以通过参数索引装配相同的字面量值,如下所示:
(c)装配集合
Sgt. Pepper's Lonely Hearts C1ub Band
with a Little Help from My Friends
Lucy in the Sky with Di amonds
Getting Better
Fixing a Hole
bean>
其中,元素是
与之类似,我们也可以使用元素替代
public Discography (String artist, List cds) { ... }
那么,你可以采取如下的方式配置Discography bean:
当构造器参数的类型是java.util. List时,使用元素是合情合理的。尽管如此,我们也可以按照同样的方式使用
Sgt. Pepper's Lonely Hearts C1ub Band
With a Little Help from My Friends
Lucy in the Sky wi th Diamonds
Getting Better
Fixing a Ho1e
.
set>
cons tructor - arg>
bean>
元素的区别不大,其中最重要的不同在于当Spring创建要装配的集合时,所创建的是java. util . set还是java.util.List。如果是Set的话,所有重复的值都会被忽略掉,存放顺序也不会得以保证。不过无论在哪种情况下,
都可以用来装配List、set甚至数组。
在装配集合方面,
(1)
例:
我们已经知道,Spring为
...
bean>
我们可以使用p-命名空间,按照以下的方式装配compactDisc属性:
p-命名空间中属性所遵循的命名约定与c-命名空间中的属性类似。下图阐述了p-命名空间属性是如何组成的。
首先,属性的名字使用了"p:"前缀,表明我们所设置的是一个属性。接下来就是要注入的属性名。最后,属性的名称以“-ref"结尾,这会提示。Spring要进行装配的是引用,而不是字面量。
(2)将字面量注入到属性中
属性也可以注入字面量,这与构造器参数非常类似。
Sgt. Pepper's Lonely Hearts C1ub Band
With a Little Help from My Friends
Lucy in the Sky with Di amonds
Getting Better
Fixing a Hole
在这里,除了使用元素来设置tracks属性,这与之前通过
另外一种可选方案就是使用p-命名空间的属性来完成该功能:
Sgt. Pepper's Lonely Hearts Club Band
with a Little Help from My Friends
Lucy in the Sky with Diamonds
Getting Better
Fixing a Hole
1ist>
与c-命名空间一样,装配bean引用与装配字面量的唯一区别在于是否带有“-ref"后缀。如果没有“-ref"后缀的话,所装配的就是字面量。但需要注意的是,我们不能使用p-命名空间来装配集合,没有便利的方式使用p-命名空间来指定一个值(或bean引用)的列表。但是,我们可以使用Spring util-命名空间中的一些功能来简化BlankDiscbean。
首先,需要在XML中声明util-命名空间及其模式:
< /beans>
util-命名空间所提供的功能之一就是
现在,我们能够像使用其他的bean那样,将磁道列表bean注入到BlankDisc bean的tracks属性中:
(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();
}
}
compactDisc ()方法已经从CDPlayerConfig中移除掉了,我们需要有一种方式将这两个类组合在一起。一种方法就是在CDPlayerConfig中使用@Import注解导入CDConfig:
package soundsystem;
import org. spr ingf ramework. context. annotation. Bean;
import org. spr ingf r amework. context。annotation. Configuration;
import org. spr ingf ramework. context . annotation. Import;
@Configuration
@Import (CDConfig.class}
public class CDP1ayerConfig {
@Bean
public CDP1ayer cdPlayer (CompactDisc compactDisc) {
return new CDP1ayer (compactDisc) ;
}
或者采用一个更好的办法,也就是不在CDPlayerConfig中使用@Import,而是创建一个更高级别的SoundSystemConfig,在这个类使用@ Import将两个配置类组合在一起:
package soundsystem;
import org. spr ingframework. context . annotation. Configuration;
import org. springfr amework. context . annotation. Import;
@Configuration
@Import ( {CDP1ayerConfig . class, CDConfig .c1ass})
public class SoundSystemConfig {
}
不管采用哪种方式,我们都将CDPlayer的配置与BlankDisc的配置分开了。现在,我们假设(基于某些原因)希望通过XML来配置BlankDisc,如下所示: .
<1ist>
Sgt. Pepper's Lonely Hearts Club Band
With a Little Help from My Friends
Lucy in the Sky with Diamonds
Getting Better
Fixing a Hole
< !-- .. .other tracks omitted for brevity. .. -->
constructor-arg>
bean>
现在BlankDisc配置在了XML之中,我们该如何让Spring同时加载它和其他基于Java的配置呢?答案是@ ImportResource注解,假设BlankDisc定义在名为cd-config. xml的文件中,该文件位于根类路径下,那么可以修改SoundSystemConfig,让它使用@ ImportResource注解,如下所示:
package soundsystem;
import org. spr ingf ramework. context . annotat ion. Conf iguration;
import org. spr ingframework . context. annotation. Import;
import org. spr ingframnework. context . annotat ion. Impor tResource;
@Conf iguration
@Import (CDP1ayerConfig. class)
@ImportResource (" classpath:cd-config. xm1")
public class SoundSystemConfig {
}
两个bean——配 置在JavaConfig中的CDPlayer以及配置在XML中BlankDisc——都会被加载到Spring容器之中。因为CDPlayer中带有@Bean注解的方法接受一个CompactDi sc作为参数,因此BlankDisc将会装配进来,此时与它是通过XML配置的没有任何关系。
(2)在XML配置中引用JavaConfig
在XML中import>元素只能导入其他的XML配置文件,并没有XML元素能够导入JavaConfig类。 但是,有一个你已经熟知的元素能够用来将Java配置导入到XML配置中:
采用这样的方式,两种配置——其中一个使用XML描述,另一个使用Java描述——被组合在了一起。类似地,你可能还希望创建一个更高层次 的配置文件,这个文件不声明任何的bean,只是负责将两个或更多的配置组合起来。例如,你可以将CDConfig bean从之前的XML文件中移除掉,而是使用第三个配置文件将这两个组合在一起:
不管使用JavaConfig还是使用XML进行装配,我通常都会创建一个根配置(root configuration),也就是这里展现的这样,这个配置会将两个或更多的装配类和/或XML文件组合起来。在根配置中启用组件扫描(通过