Spring in Action(Spring实战)的第二章讲述了如何装配bean。
Spring中装配的主要方式
XML显式配置
Java显式配置
使用Java隐式扫描bean并自动装配
三种方式各有好处,作者认为选哪种只是口味问题,但强烈建议使用第三种(使用Java隐式扫描bean并自动装配),并且建议即使要用显式配置,也尽量使用Java配置,因为Java更好用(powerful),具有类型安全检查,并且更好重构。只有在XML有命名空间可以很方便使用,而JavaConfig中没有的时候才使用XML显式配置。
使用Java隐式扫描bean并自动装配 Spring的这种方案实在是太好用了,应该尽量利用这种机制。这个方案中,我们要做的事情包括两个方面:让bean可以被扫描以及在使用bean的地方声明注入bean。这样,Spring就可以扫描并生成bean,并且在要使用的地方注入bean了。
让bean可以被扫描到
在类上加@Component标签,标记这个类可以作为组件供其它类使用。
写一个Java配置类,加上@Configuration和@ComponentScan标签,(也可以用xml实现,略)
在启动项目时,Spring就会在这个配置类所在的目录下查找有加@Component标签的类,并且自动生成bean了。
在类上加@Component标签:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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.print("Playing " + title + " by " + artist); } }
配置类加加上@Configuration和@ComponentScan标签:
1 2 3 4 5 6 7 8 9 10 package soundsystem;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;@Configuration @ComponentScan public class CDPlayerConfig {}
标注被扫描bean的位置 有时候,配置类和要扫描的类不在同一个目录下,此时要标明要扫描的类所在的包或者直接标明要扫描的类。
1 2 3 4 @Configuration @ComponentScan(basePackages={"soundsystem", "video"}) public class CDPlayerConfig {}
或
1 2 3 4 5 @Configuration @ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class}) public class CDPlayerConfig {}
为bean定义id Spring会默认将类的第一个字母改为小写,作为bean的id,如果一个类有多个bean,或者只是想改用其它id,可以通过以下两种方式,定义bean的id:
1 2 3 4 5 6 @Component("lonelyHeartsClub") public class SgtPeppers implements CompactDisc {... }
或
1 2 3 4 5 6 package soundsystem;import javax.inject.Named;@Named("lonelyHeartsClub") public class SgtPeppers implements CompactDisc {... }
在使用bean的地方声明注入bean 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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(); } }
要使用bean的地方(类成员变量,构造函数或普通方法都可以),加 @Autowired或 @Inject标签,注入bean,就可以开始使用了。从Autowired或和Inject的名称可以看出,其实wire(装配)和inject(注入)的意思差不多,都是表示将bean连接起来。其实wire的意思就是连接。
因为只有SgtPeppers实现了CompactDisc,所以上面的例子会自动注入一个SgtPeppers实例,有时,有多个bean都实现了CompactDisc接口,那么在注入时,需要声明注入的bean的id。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component public class CDPlayer implements MediaPlayer { private CompactDisc cd; @Autowired @Qualifier("lonelyHeartsClub") public CDPlayer (CompactDisc cd) { this .cd = cd; } public void play () { cd.play(); } }
验证自动化注入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @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" , log.getLog()); } }
为了避免不同操作系统换行符的不同,我将原代码中的换行符去掉了,相应的,将SgtPeppers的play方法的println也改成print了。
在Java配置文件中显式注册bean 定义配置文件 1 2 3 4 5 6 7 8 9 10 11 @Configuration public class CDPlayerConfig { @Bean public CompactDisc sgtPeppers () { return new SgtPeppers(); } }
CDPlayerConfig加上Configuration标签,标注为配置文件,但是去掉了ComponentScan标签,所以要显式定义bean。
使用Bean标签 Bean标签表示该方法返回的bean会被注册到Spring容器里,并且id和方法名称一样,也可以使用name参数定义id。 1 2 3 4 @Bean(name="lonelyHeartsClubBand") public CompactDisc sgtPeppers () { return new SgtPeppers(); }
下面的例子通过构造函数将CompactDisc注入CDPlayer: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration public class CDPlayerConfig { @Bean public CompactDisc sgtPeppers () { return new SgtPeppers(); } @Bean public CDPlayer cdPlayer () { return new CDPlayer(sgtPeppers()); } @Bean public CDPlayer anotherCDPlayer () { return new CDPlayer(sgtPeppers()); } }
Config文件中,两个方法内调用相同的Bean方法,是否会生成两个sgtPeppers实例?如果没有使用Spring的话,是会有两个实例,使用Spring机制之后,CDPlayer并不是在调用sgtPeppers(),而是从Spring容器中获取bean,所以只有一个bean。这个调用过程有些让人困惑,所以,更好的方式是直接引用bean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration public class CDPlayerConfig { @Bean public CompactDisc compactDisc () { return new SgtPeppers(); } @Bean public CDPlayer cdPlayer (CompactDisc compactDisc) { return new CDPlayer(compactDisc); } @Bean public CDPlayer anotherCdPlayer (CompactDisc compactDisc) { return new CDPlayer(compactDisc); } }
在这里,cdPlayer和anotherCdPlayer这两个方法,因为在Java配置文件中,并且加了@Bean标签,都会尝试自动注入CompactDisc类型的bean。Spring会通过扫描目录,查找Java配置文件中带Bean标签的方法,甚至查找XML配置文件来找到需要的bean。在这里例子中,是通过在带有Configuration的Java配置文件CDPlayerConfig(这里正好是同一个文件)中查找带有Bean标签的compactDisc()方法,找到对应的bean的,这个bean是一个SgtPeppers实例。
验证使用Java配置文件显示注册bean 1 2 3 4 5 6 7 8 9 10 11 12 public class SgtPeppers implements CompactDisc { private String title = "Sgt. Pepper's Lonely Hearts Club Band" ; private String artist = "The Beatles" ; private int times = 0 ; public void play () { times++; System.out.print("Playing " + title + " by " + artist + "," + times); } }
以下是测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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.beans.factory.annotation.Qualifier;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 @Qualifier("cdPlayer") private MediaPlayer player; @Autowired @Qualifier("anotherCdPlayer") private MediaPlayer anotherPlayer; @Test public void play () { player.play(); assertEquals( "Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles,1" , log.getLog()); log.clear(); anotherPlayer.play(); assertEquals( "Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles,2" , log.getLog()); } }
笔者注:我在SgtPeppers类中加了个成员变量times,这个变量不是static的,而是对象级别的,所以通过验证times是否递增,可以验证两个player是否使用的是同一个bean。测试通过,说明使用的确实是同一个bean。另外,为了避免不同操作系统换行符的不同,我将原代码中的换行符去掉了,相应的,将SgtPeppers的play方法的println也改成print了。
在XML配置文件中显式注册bean 略
混用Java和XML配置文件显式注册bean 略