Spring提供了三种主要的bean装配机制:
- 在XML中进行显式配置
- 在Java中进行显式配置
- 隐式的bean发现机制和自动装配
使用优先级:自动化装配>Java>XML
尽可能地使用自动配置的机制。显式配置越少越好。当必须要显式配置bean的时候(比如,有些源码不是由你来维护的,而当你需要为这些代码配置bean的时候),推荐使用JavaConfig。因为JavaConfig更强大、类型安全并且对重构友好。最后,只有当你想要使用便利的XML命名空间,并且在JavaConfig中没有同样的实现时,才应该使用XML。
自动化装配
Spring从两个角度来实现自动化装配:
- 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
- 自动装配(autowiring):Spring自动满足bean之间的依赖。
创建可被发现的bean
用CD和CD播放器来举例,首先定义一个CD接口:
package soundsystem;
public interface CompactDisc {
void play();
}
CD接口的一个实现类:
package soundsystem;
@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);
}
}
- @Component注解:表明该类会作为组件类,并告知Spring要为这个类创建bean。
没有必要显式配置SgtPeppers bean,因为这个类使用了@Component注解,所以Spring会为你把事情处理妥当。
不过,组件扫描默认是不启用的。我们还需要显式配置一下Spring,从而命令它去寻找带有@Component注解的类,并为其创建bean。以下配置类展现了完成这项任务的最简洁配置。
package soundsystem;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
- @ComponentScan注解:在配置类上使用该注解,能够在Spring中启用组件扫描。
如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为CDPlayerConfig类位于soundsystem包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样的话,就能发现CompactDisc,并且会在Spring中自动为其创建一个bean。
为了测试组件扫描的功能,我们创建一个简单的JUnit测试,它会创建Spring上下文,并判断CompactDisc是不是真的创建出来了:
package soundsystem;
import static org.junit.Assert.*;
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);
}
}
CDPlayerTest使用了Spring的SpringJUnit4ClassRunner,以便在测试开始的时候自动创建Spring的应用上下文。注解@ContextConfiguration会告诉它需要在CDPlayerConfig中加载配置。因为CDPlayerConfig类中包含了@ComponentScan,因此最终的应用上下文中应该包含CompactDisc bean。
为了证明这一点,在测试代码中有一个CompactDisc类型的属性,并且这个属性带有@Autowired注解,以便于将CompactDiscbean注入到测试代码之中。最后,会有一个简单的测试方法断言cd属性不为null。如果它不为null的话,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码之中。
为组件扫描的bean命名
Spring应用上下文中所有的bean都会给定一个ID。没有显示配置的话默认为类名第一个字母小写。(前面的例子:sgtPeppers)。
如果想为这个bean设置不同的ID,你所要做的就是将期望的ID作为值传递给@Component注解(@Component(“期望的ID”))。比如说,如果想将SgtPeppers bean标识为lonelyHeartsClub,那么你需要将SgtPeppers类的@Component注解配置为如下所示:
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}
@Name注解和@Component注解有一些细微差异,在大部分情况下可以相互替换。
设置组件扫描的基础包
在@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类型外,可以指定为包中所包含的类或接口,这些类所在的包将会作为组件扫描的基础包。此时不再是basePackages而是basePackageClasses:
@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class,DVDPlayer.class})
public class CDPlayerConfig {
}
可以考虑在包中创建一个用来进行扫描的空标记接口(marker interface)。通过标记接口的方式,你依然能够保持对重构友好的接口引用,但是可以避免引用任何实际的应用程序代码(在稍后重构中,这些应用代码有可能会从想要扫描的包中移除掉)。
通过为bean添加注解实现自动装配
自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
- 构造器上添加@Autowired注解:这表明当Spring创建该bean的时候,会通过这个构造器来进行实例化并且会传入一个其所依赖的bean。
package soundsystem;
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
当Spring创建CDPlayer bean的时候,会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc类型的bean。
@Autowired注解不仅能够用在构造器上,还能用在类方法(如setter方法)上。比如说,如果CDPlayer有一个setCompactDisc()方法,那么可以采用如下的注解形式进行自动装配:
@AutoWired
public void setCompactDisc(CompactDisc cd){
this.cd = cd;
}
不管是构造器、Setter方法还是其他的方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。
如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常。为了避免异常的出现,你可以将@Autowired的required属性设置为false:
@AutoWired(required = false)
public void setCompactDisc(CompactDisc cd){
this.cd = cd;
}
将required属性设置为false,自动装配时没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。在代码中应当进行null检查,否则可能会出现NullPointerException。
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表明没有明确指定要选择哪个bean进行自动装配。
@Inject和@Autowired有一些细微差别,但在大多数情况下可以互相替换。
通过Java代码装配bean
当想要将第三方库中的组件装配到应用中时,是没有办法在它的类上添加@Component和@Autowired注解的,因此就不能使用自动化装配的方案了。在这种情况下,必须要采用显式装配的方式。
JavaConfig与其他的Java代码有所区别。在概念上,它与应用程序中的业务逻辑和领域代码是不同的。尽管它与其他的组件一样都使用相同的语言进行表述,但JavaConfig是配置代码。这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。尽管不是必须的,但通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了。
创建配置类
创建JavaConfig类的关键在于为其添加@Configuration注解。
- @Configuration注解:表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。
@Configuration
public class CDPlayerConfig{
}
尽管可以同时使用组件扫描和显式配置,但是本节更加关注于显式配置,因此将CDPlayerConfig的@ComponentScan注解移除掉了。
声明简单的bean
要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@Bean注解。比方说,下面的代码声明了CompactDisc bean:
@Bean
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
- @Bean注解:会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。
默认情况下,bean的ID与带有@Bean注解的方法名是一样的。在本例中,bean的名字将会是sgtPeppers。如果你想为其设置成一个不同的名字的话,那么可以重命名该方法,也可以通过name属性指定一个不同的名字:
@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
不管你采用什么方法来为bean命名,bean声明都是非常简单的。方法体返回了一个新的SgtPeppers实例。这里是使用Java来进行描述的,因此我们可以发挥Java提供的所有功能,只要最终生成一个CompactDisc实例即可。比如在一组CD中随机选择一个CompactDisc来播放:
@Bean
public CompactDisc randomBeatlesCD(){
int choice = (int) Math.floor(Math.random() * 3);
if(choice == 0){
return new SgtPeppers();
}else if(choice == 1){
return new HardDayNight();
}else{
return new Revolver();
}
}
借助JavaConfig实现注入
通过构造器实现:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
通过Setter方法实现:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
CDPlayer cdPlayer = new CDPlayer();
cdPlayer.setCompactDisc(compactDisc);
return cdPlayer;
}
再次强调,带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例。构造器和Setter方法只是@Bean方法的两个简单样例。这里所存在的可能性仅仅受到Java语言的限制。
通过这种方式引用其他的bean通常是最佳的选择,因为它不会要求将CompactDisc声明到同一个配置类之中。在这里甚至没有要求CompactDisc必须要在JavaConfig中声明,实际上它可以通过组件扫描功能自动发现或者通过XML来进行配置。你可以将配置分散到多个配置类、XML文件以及自动扫描和装配bean之中,只要功能完整健全即可。不管CompactDisc是采用什么方式创建出来的,Spring都会将其传入到配置方法中,并用来创建CDPlayer bean。
默认情况下,Spring中的bean都是单例的。假设此时有第二个CDPlayer,我们并没有必要为第二个CDPlayer bean创建完全相同的CompactDisc bean。所以,Spring会拦截对sgtPeppers()的调用并确保返回的是Spring所创建的bean,也就是Spring本身在调用sgtPeppers()时所创建的CompactDiscbean。因此,两个CDPlayer bean会得到相同的SgtPeppers实例。