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标签:
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标签:
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
标注被扫描bean的位置
有时候,配置类和要扫描的类不在同一个目录下,此时要标明要扫描的类所在的包或者直接标明要扫描的类。
@Configuration
@ComponentScan(basePackages={"soundsystem", "video"})
public class CDPlayerConfig {}
或
@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {}
为bean定义id
Spring会默认将类的第一个字母改为小写,作为bean的id,如果一个类有多个bean,或者只是想改用其它id,可以通过以下两种方式,定义bean的id:
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}
或
package soundsystem;
import javax.inject.Named;
@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}
在使用bean的地方声明注入bean
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。代码如下:
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
@Qualifier("lonelyHeartsClub")
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
验证自动化注入
@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
定义配置文件
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
}
CDPlayerConfig加上Configuration标签,标注为配置文件,但是去掉了ComponentScan标签,所以要显式定义bean。
使用Bean标签
Bean标签表示该方法返回的bean会被注册到Spring容器里,并且id和方法名称一样,也可以使用name参数定义id。
@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
下面的例子通过构造函数将CompactDisc注入CDPlayer:
@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:
@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
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);
}
}
以下是测试代码:
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
略
欢迎扫描下方二维码关注微信公众号【谈谈IT】,第一时间获取最新文章。