装配Bean
[TOC]
Spring装配bean的可选方案
装配:创建应用对象之间协作关系的行为通常称为装配,这这也是依赖注入的本质。
Spring装配bean的三种方案:
- 在XML中进行显示配置
- 在Java中进行显示配置
- 隐式的bean发现机制和自动装配
自动化装配bean
Spring有两种角度来实现自动化装配
- 组件扫描:Spring会自动发现应用上下文中所创建的bean
- 自动装配:Spring自动满足bean之间的依赖
创建可被发现的bean
CompactDisc.class - CD接口
package soundsystem.compactdisc;
public interface CompactDisc {
void play();
}
SgtPeppers.class - CD接口实现类:一张专辑唱片
package soundsystem.compactdisc;
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.print("Playing " + title + " by " + artist);
}
}
Component
注解:表明该类会作为组件类,并告知Spring要为这个类创建bean。
除此之外,组件扫描默认是不启动的,我们需要显示配置Spring,从而命令它去寻找Component
类:
通过Java注解的方式开启组件扫描
CDPlayerConfig.class - 定义Spring装配规则
package soundsystem.config;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = {"soundsystem"})
public class CDPlayerConfig {
}
在这个项目中,我的分包如下图所示:
如果没有其他配置的话,@ComponentScan 默认会扫描与配置类相同的包以及该包下所有的子包,查找带有@Component 注解的类,这样的话,Spring会自动为其创建bean。
CDPlayerTest.class - 测试文件
package soundsystem.test;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
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 soundsystem.compactdisc.CompactDisc;
import soundsystem.config.CDPlayerConfig;
import soundsystem.mediaplayer.MediaPlayer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
@Rule
public final SystemOutRule log = new SystemOutRule().enableLog();
@Autowired
private MediaPlayer mediaPlayer;
@Autowired
private CompactDisc compactDisc;
@Test
public void cdShouldNotBeNull(){
assertNotNull(compactDisc);
}
@Test
public void play(){
mediaPlayer.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles",log.getLog());
}
}
SpringJUnit4ClassRunner
可以在测试开始的时候自动创建Spring应用上下文,@ContextConfiguration
告诉测试类需要在CDPlayerConfig
中加载配置。
通过XML的方式开启组件扫描
spring-config.xml
在这个项目中,我的分包如下图所示:
CDPlayerTest.class - 测试文件
package soundsystem.test;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.SystemOutRule;
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 soundsystem.compactdisc.CompactDisc;
import soundsystem.mediaplayer.MediaPlayer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-config.xml"})
public class CDPlayerTest {
@Rule
public final SystemOutRule log = new SystemOutRule().enableLog();
@Autowired
private MediaPlayer mediaPlayer;
@Autowired
private CompactDisc compactDisc;
@Test
public void cdShouldNotBeNull(){
assertNotNull(compactDisc);
}
@Test
public void play(){
mediaPlayer.play();
assertEquals("Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles",log.getLog());
}
}
给bean起一个不同的名称
@Component(value = "lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
// ...
}
@Component(value = "lonelyHeartsClub") : 给bean设置不同的ID
设置组件扫描的基础包
@ComponentScan(basePackages = {"soundsystem","video"})
public class CDPlayerConfig {
// ...
}
@ComponentScan(basePackages = {"soundsystem","video"}) 可以扫描多个基础包;但是这样是类型不安全的。
除了将包设置为String之外,@ComponentScan 还提供了另一种格式,那就是将其指定为包中的类或者接口:
@ComponentScan(basePackageClasses = {CompactDisc.class, CDPlayer.class})
public class CDPlayerConfig {
}
这些类所在包将会作为组件扫描的基础包。
通过为bean添加注解实现自动装配
自动装配:自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring应用上下文中寻找匹配某个bean需求的其他bean 。为了声明要进行自动匹配,我们可以借助Spring中的 @Autowired
注解 。
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
@Autowired
public CDPlayer(CompactDisc compactDisc){
this.compactDisc = compactDisc;
}
@Override
public void play() {
compactDisc.play();
}
}
上段代码表明:当Spring创建CDPlayer bean的时候,会通过构造器来进行实例化并传入一个可设置给CompactDisc 类型的bean
@Autowired 不仅可以用在构造器上,还可以用在属性的Setter方法(或其他任何方法)上。
@Autowired
public void insertDisc(CompactDisc compactDisc){
this.compactDisc = compactDisc;
}
假如有且只有一个bean匹配依赖需求的话,那么这个bean将会被匹配进来。
如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一个异常,为了避免异常的出现,可以将@Autowired的required属性设置为false
@Autowired(required = false)
public void insertDisc(CompactDisc compactDisc){
this.compactDisc = compactDisc;
}
将@Autowired的required属性设置为false时,Spring会尝试自动匹配,如果匹配不到相应的bean,Spring会将这个bean处于未装配的状态。如果你的代码中没有进行null检查的话,这个处于未装配状态的属性可能会出现NullPointerException 。
如果有多个bean都能满足依赖关系的话,Spring将会抛出一个异常,表示没有明确指定要选择哪一个bean进行自动装配。
通过Java代码装配bean
当你需要将第三方库组件装配到你的应用中,是没有办法在它的类上加@Component和@Autowired注解的,这时候我们就需要显示的装配了。有两种可选方案:Java和XML 。
创建配置类
@Configuration
public class CDPlayerConfig {
}
@Configuration 注解表明这个类是一个配置类,该类应该包含在Spring应用上下文如何创建bean的细节。
声明简单的bean
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
@Bean 注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean 。
默认情况下,bean的ID与带有@Bean注解的方法名是一样的,但是,你也可以指定一个不同的名字:
@Bean(name = "lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
借助JavaConfig实现注入
CDPlayerConfig.class - JavaConfig 类
@Configuration
public class CDPlayerConfig {
@Bean(name = "lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}
}
看起来,CompactDisc是通过调用 sgtPeppers()
得到的,但情况并非完全如此,因为 sgtPeppers()
方法上添加了@Bean注解,Spring 将会拦截所有对它的调用,并确保直接返回该方法所创建的bean , 而不是每一次都对其进行实际的调用。
假设你引入了其他的CDPlayer的bean:
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(sgtPeppers());
}
@Bean
public CDPlayer anthorCDPlayer(CompactDisc compactDisc){
return new CDPlayer(sgtPeppers());
}
默认情况下,SPirng中的bean都是单例的,所以,Spring会拦截对sgtPeppers()的调用并确保返回的是Sprring所创建的bean , 在这里,bean就是Spirng在调用sgtPeppers()时所创建的CompactDisc bean 。
还有另一种写法:
@Configuration
public class CDPlayerConfig {
@Bean(name = "lonelyHeartsClubBand")
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
在Spring调用cdPlayer() 创建 CDPlayer bean 的时候,会自动装配一个CompactDisc 到配置方法中。不管CompactDisc是通过什么方式创建出来的,SPring都会将其传入到配置方法中,并用来创建CDPlyaer bean
通过XML装配bean
spring-config.xml
当spring发现
借助构造器初始化bean
有两种基本方案可供选择:
-
元素 - 使用Spring3.0 引入的 c-命名空间
有些事情
可以做到,但是使用c-命名空间却无法实现
构造器注入bean的引用
使用
元素:
使用c-命名空间的方案:
属性名以c: 开头,也就是命名空间的前缀,表示是构造器初始化。 接下来是要装配的构造器参数名称,-ref表正在装配的是一个bean的引用,这个bean的名称是compactDisc(以图为例)
代替方案如下:
c:_0-ref 中的 _0 表示参数的索引 , 使用索引来识别会比使用名称识别构造器参数更好一些。
将字面量(字符串)注入到构造器中
BlankDisc.class
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.print("Playing " + title + " by " + artist);
}
}
spring-config.xml
标签中的ref属性表示引用了其他的bean,而value属性表明给定的值要以字符串的形式注入到构造器中
更为简单的写法:
spring-config.xml ———— 通过索引
装配集合
在装配bean引用或者字符串的方面,
和 c-命名空间的功能是相同的,但有一种情况是
能够实现,但是c-命名空间不能实现的 : 那就是 集合的装配
BlankDisc.class ———— 引入磁道的概念
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
/**
* 磁道
*/
private List tracks;
public BlankDisc(String title, String artist, List tracks) {
this.title = title;
this.artist = artist;
this.tracks = tracks;
}
@Override
public void play() {
System.out.print("Playing " + title + " by " + artist);
for(String track:tracks){
System.out.println("-Track : " +track);
}
}
}
spring-config.xml
track1
track2
track3
track4
与之类似的,我们也可以使用 代替
, 来实现bean引用列表的装配
当磁道为set 类型的时候,我们可以使用
而非
:
track1
track2
track3
track4
借助setter初始化bean
CDPlayer.class
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
public void setCompactDisc(CompactDisc compactDisc){
this.compactDisc = compactDisc;
}
@Override
public void play() {
compactDisc.play();
}
}
spring-config.xml
Spring中
元素提供了 c-命名空间作为替代方案,与之类似,Spring提供了更加简洁的p-命名空间,作为
元素的替代方案。
spring-config.xml
将字面值注入到属性中
BlankDisc.class
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List tracks;
public void setTitle(String title) {
this.title = title;
}
public void setArtist(String artist) {
this.artist = artist;
}
public void setTracks(List tracks) {
this.tracks = tracks;
}
@Override
public void play() {
System.out.print("Playing " + title + " by " + artist);
for (String track:tracks){
System.out.println("- Track : " + track);
}
}
}
spring-config.xml
track1
track2
track3
同样的,也可以使用p-命名空间的方式:
track1
track2
track3
我们可以使用util-命名空间的方式来简化bean:
我们可以把磁道列表转移到blankDisc bean
之外,并将其声明到单独的bean 之中,如下所示:
track1
track2
这样,我们就可以简化blankDisc bean的定义了:
导入和混合配置
在JavaConfig中引用XML配置
我们假设CDPlayerConfig有一些笨重,需要将blankDisc
从CDPlayerConfig
中拆分出来。定义到它自己的CDConfig
类中:
CDConfig.class
@Configuration
public class CDConfig {
@Bean
public CompactDisc compactDisc(){
return new SgtPeppers();
}
}
CDPlayerConfig.class
@Configuration
public class CDPlayerConfig {
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
我们需要将这两个config类组合到一起,第一种方法是直接在CDPlayerConfig.class
使用@Import注解导入CDConfgi.class
@Configuration
@Import(CDConfig.class)
public class CDPlayerConfig{
// ...
}
更高级的办法是创建一个新的Config,将两个配置类组合到一起:
@Configuration
@Import({CDConfig.class,CDPlayerConfig.class})
public class SoundSystemConfig {
}
假设我们通过XML的方式配置BlankDisc:
spring-config.xml
track1
track2
track3
使用@ImportResource注解,加载XML文件
@Configuration
@Import({CDConfig.class,CDPlayerConfig.class})
@ImportResource({"classpath:spring-context.xml"})
public class SoundSystemConfig {
}
在XML配置中引用JavaConfig
cd-config.xml
track1
track2
track3
spring-context.xml
元素可以导入其他的XML配置文件,但是不可以导入JavaConfig类
所以,我们可以使用下面的方法引用:
小结
Spring中装配bean的三种主要方式:自动化配置,基于Java的显示配置,以及基于XML的显示配置