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

字数1,930 大约花费9分钟

目录

  1. 1. Spring 中装配的主要方式
  2. 2. 使用 Java 隐式扫描 bean 并自动装配
    1. 2.1. 让 bean 可以被扫描到
    2. 2.2. 标注被扫描 bean 的位置
    3. 2.3. 为 bean 定义 id
    4. 2.4. 在使用 bean 的地方声明注入 bean
    5. 2.5. 验证自动化注入
  3. 3. 在 Java 配置文件中显式注册 bean
    1. 3.1. 定义配置文件
    2. 3.2. 使用 Bean 标签
    3. 3.3. 验证使用 Java 配置文件显示注册 bean
  4. 4. 在 XML 配置文件中显式注册 bean
  5. 5. 混用 Java 和 XML 配置文件显式注册 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
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
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class CDPlayerConfig {
}

标注被扫描 bean 的位置

有时候,配置类和要扫描的类不在同一个目录下,此时要标明要扫描的类所在的包或者直接标明要扫描的类。

1
2
3
@Configuration
@ComponentScan(basePackages={"soundsystem", "video"})
public class CDPlayerConfig {}

1
2
3
4

@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {}

为 bean 定义 id

Spring 会默认将类的第一个字母改为小写,作为 bean 的 id,如果一个类有多个 bean,或者只是想改用其它 id,可以通过以下两种方式,定义 bean 的 id:

1
2
3
4
5

@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
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

@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

@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
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的文章均为原创或翻译(翻译会注明外文来源),转载请以链接形式标明本文地址: http://tantanit.com/springinacton-di-si-ban-di-er-zhang-zhuang-pei-bean-du-shu-bi-ji/

谈谈IT

欢迎关注官方微信公众号获取最新原创文章