注:本文大部分内容出自《Spring实战(第4版)》
一、Spring配置的可选方案
Spring容器负责创建应用程序中的bean并通过DI来协调这些对象之间的关系。但是,作为开发人员,你需要告诉Spring要创建哪些bean并且如何将其装配在一起。
当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:
- 在XML中进行显式配置。
- 在Java中进行显式配置。
- 隐式的bean发现机制和自动装配。
注:本文只介绍Java配置方式,XML方式请参阅原书
Spring的配置风格是可以互相搭配的, 所以你可以选择使用XML装配一些bean, 使用Spring基于Java的配置(JavaConfig) 来装配另一些bean, 而将剩余的bean让Spring去自动发现。
建议尽可能地使用自动配置的机制,显式配置越少越好。
二、自动化装配bean
Spring从两个角度来实现自动化装配:
- 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
- 自动装配(autowiring):Spring自动满足bean之间的依赖。
1、创建可被发现的bean
CD为我们阐述DI如何运行提供了一个很好的样例。 如果你不将CD插入(注入) 到CD播放器中, 那么CD播放器其实是没有太大用
处的。 所以, 可以这样说, CD播放器依赖于CD才能完成它的使命。下面的代码定义了一个CD接口
CompactDisc接口在Java中定义了CD的概念
package soundsystem;
public interface CompactDisc{
void play();
}
作为接口,它定义了CD播放器对一盘CD所能进行的操作。它将CD播放器的任意实现与CD本身的耦合降低到了最小的程度。
我们首先会创建CompactDisc其中的一个实现,也就是如下所示的SgtPeppers类:
带有@Component注解的CompactDisc实现类SgtPeppers
package sounddsystem;
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。
@ComponentScan注解启用了组件扫描
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig{
}
类CDPlayerConfig通过Java代码定义了Spring的装配规则,并没有显式地声明任何bean,只不过它使用了@ComponentScan注解,这个注解能够在Spring中启用组件扫描。
如果没有其他配置的话,@ComponentScan默认会扫描与配置类相同的包。因为CDPlayerConfig类位于soundsystem包中,因此Spring将会扫描这个包以及这个包下的所有子包,查找带有@Component注解的类。这样的话,就能发现CompactDisc,并且会在Spring中自动为其创建一个bean。
创建一个简单的JUnit测试,它会创建Spring上下文,测试组件扫描能够发现CompactDisc程序清单:
测试组件扫描能够发现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,因此最终的应用上下文中应该包含CompactDiscbean。
带有@Autowired注解,以便于将CompactDiscbean注入到测试代码之中。最后,会有一个简单的测试方法断言cd属性不为null。 如果它不为null的话,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码之中。
2、为组件扫描的bean命名
Spring应用上下文中所有的bean都会给定一个ID。尽管我们没有明确地为SgtPeppersbean设置ID,但Spring会根据类名为其指定一个ID。这个bean所给定的ID为sgtPeppers,也就是将类名的第一个字母变为小写。如果想为这个bean设置不同的ID,你所要做的就是将期望的ID作为值传递给@Component注解。
@Component注解配置为如下所示:
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc{
...
}
还有另外一种为bean命名的方式,这种方式不使用@Component注解,而是使用Java依赖注入规范(Java Dependency Injection)中所提供的@Named注解来为bean设置ID:
@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc{
......
}
Spring支持将@Named作为@Component注解的替代方案。 两者之间有一些细微的差异, 但是在大多数场景中, 它们是可以互相替换的。
3、设置组件扫描的基础包
按照默认规则, 会以配置类所在的包作为基础包,若想扫描不同的包,可通过设置@ComponentScan的value属性指明基础包来扫描组件:
@Configuration
@ComponentScan(basePackages="soundsystem")
public class CDPlayerConfig{}
basePackages属性使用的是复数形式,可以设置多个基础包:
@Configuration
@ComponentScan(basePackages="soundsystem","video")
public class CDPlayerConfig{}
在上面的例子中,所设置的基础包是以String类型表示的,但这种方法是类型不安全(not type-safe)的。
@ComponentScan还可以通过basePackageclasses将其指定为包中所包含的类或接口:
@Configuration
@ComponentScan(basePackageclasses={CDPlayer.class,DVDPlayer.class})
public class CDPlayerConfig{}
4、通过为bean添加注解实现自动装配
简单来说,自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean。为了声明要进行自动装配,我们可以借助Spring的@Autowired注解。
如下面代码中的CDPlayer类,它的构造器上添加了@Autowired注解, 这表明当Spring创建CDPlayerbean的时候, 会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc类型的bean。
通过自动装配, 将一个CompactDisc注入到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 CDPlayer(CompactDisc cd){
this.cd = cd;
}
public void play(){
cd.play();
}
}
@Autowired注解不仅能够用在构造器上,还能用在类的任何方法上。
@Autowired
public void setCompactDisc(CompactDisc cd){
this.cd = cd;
}
不管是什么方法,Spring都会尝试满足方法参数上所声明的依赖。假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被装配进来。
下面来看一下在Spring中如何显式地装配bean, 以Java代码编写配置为例。
三、通过Java代码装配bean
在很多场景下通过组件扫描和自动装配实现Spring的自动化配置是更为推荐的方式, 但有时候自动化配置的方案行不通, 比如,你想要将第三方库中的组件装配到你的应用中, 但是没有办法在它的类代码上添加@Component和@Autowired注解。这种情况下, 你必须要采用显式装配的方式(Java、XML)。
JavaConfig是配置代码,这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码之中。通常会将JavaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来。
1、创建配置类
在前面见过的CDPlayerConfig中:
package soundsystem;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDPlayerConfig{
}
创建JavaConfig类的关键在于为其添加@Configuration注解,表明这是一个配置类。
2、声明简单的bean
要在JavaConfig中声明bean,我们需要编写一个方法,这个方法会创建所需类型的实例,然后给这个方法添加@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();
}
3、借助JavaConfig实现注入
假设我们需要声明CDPlayerbean,它依赖于CompactDisc:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
在这里, cdPlayer()方法请求一个CompactDisc作为参数。当Spring调用cdPlayer()创建CDPlayerbean的时候, 它会自动装配一个CompactDisc到配置方法之中。然后,方法体就可以按照合适的方式来使用它。借助这种技术,cdPlayer()方法也能够将CompactDisc注入到CDPlayer的构造器中, 而且不用明确引用CompactDisc的@Bean方法。
它不会要求将CompactDisc声明到同一个配置类之中。实际上它可以通过组件扫描功能自动发现或者通过XML来进行配置。你可以将配置分散到多个配置类、XML文件以及自动扫描和装配bean之中,只要功能完整健全即可。
通过XML装配bean、在JavaConfig中引用XML配置、在XML配置中引用JavaConfig未讲解