《Spring in Acton》第四版第二章《装配bean》读书笔记

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可以被扫描到

  1. 在类上加@Component标签,标记这个类可以作为组件供其它类使用。
  2. 写一个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

© 2022 谈谈IT All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero