Spring筆記之裝配bean
原文鏈接地址:http://tantanit.com/springinacton-di-si-ban-di-er-zhang-zhuang-pei-bean-du-shu-bi-ji/
Spring in Action(Spring實戰)的第二章講述了如何裝配bean。本文是對這章的讀書筆記,講解Spring裝配bean的三種主要方式。
# Spring中裝配的主要方式
* XML顯式配置
* Java顯式配置
* 使用Java隱式掃描bean並自動裝配
三種方式各有好處,作者認為選哪種只是口味問題,但強烈建議使用第三種(使用Java隱式掃描bean並自動裝配),並且建議即使要用顯式配置,也盡量使用Java配置,因為Java更好用(powerful),具有類型安全檢查,並且更好重構。只有在XML有命名空間可以很方便使用,而JavaConfig中沒有的時候才使用XML顯式配置。
Advertisements
# 使用Java隱式掃描bean並自動裝配
Spring的這種方案實在是太好用了,應該盡量利用這種機制。這個方案中,我們要做的事情包括兩個方面:讓bean可以被掃描以及在使用bean的地方聲明注入bean。這樣,Spring就可以掃描並生成bean,並且在要使用的地方注入bean了。
## 讓bean可以被掃描到
1. 在類上加@Component標籤,標記這個類可以作為組件供其它類使用。
2. 寫一個Java配置類,加上@Configuration和@ComponentScan標籤,(也可以用xml實現,略)
在啟動項目時,Spring就會在這個配置類所在的目錄下查找有加@Component標籤的類,並且自動生成bean了。
Advertisements
在類上加@Component標籤:
```java
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標籤:
```java
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
```
## 標註被掃描bean的位置
有時候,配置類和要掃描的類不在同一個目錄下,此時要標明要掃描的類所在的包或者直接標明要掃描的類。
```java
@Configuration
@ComponentScan(basePackages={"soundsystem", "video"})
public class CDPlayerConfig {}
```
或
```java
@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class})
public class CDPlayerConfig {}
```
## 為bean定義id
Spring會默認將類的第一個字母改為小寫,作為bean的id,如果一個類有多個bean,或者只是想改用其它id,可以通過以下兩種方式,定義bean的id:
```java
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}
```
或
```java
package soundsystem;
import javax.inject.Named;
@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
...
}
```
## 在使用bean的地方聲明注入bean
```java
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。代碼如下:
```java
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
@Qualifier("lonelyHeartsClub")
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
cd.play();
}
}
```
## 驗證自動化注入
```java
@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
## 定義配置文件
```Java
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
}
```
CDPlayerConfig加上Configuration標籤,標註為配置文件,但是去掉了ComponentScan標籤,所以要顯式定義bean。
## 使用Bean標籤
Bean標籤表示該方法返回的bean會被註冊到Spring容器里,並且id和方法名稱一樣,也可以使用name參數定義id。
```java
@Bean(name="lonelyHeartsClubBand")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
```
下面的例子通過構造函數將CompactDisc注入CDPlayer:
```java
@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:
```java
@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
```java
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);
}
}
```
以下是測試代碼:
```java
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
略