Spring中隱式的bean發現機制和自動裝配
儘管Spring的配置風格是可以互相搭配的,但是應該儘可能的使用自動配置的機制,顯式配置越少越好
Spring從兩個角度來實現自動化裝配:
- 元件掃描:Spring會自動發現應用上下文中所建立的bean
- 自動裝配:Spring會自動滿足bean之間的依賴
元件掃描和自動裝配組合在一起就可以將所需要的顯式配置降低到最少。
首先定義一個代表了CD概念的介面CompactDisc:
public interface CompactDisc {
void play();
}
作為一個介面,它將CD播放器和CD之間的耦合降低到了最小的程度。
下面建立一個CD介面的實現SgtPeppers類:
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) ;
}
}
注意在這裡使用了@Component註解,這個註解表明該類會作為元件類,並告知Spring為該類建立bean。只需要使用了這個註解,就沒有必要顯式配置SgtPeppers bean,Spring會把所有任務處理妥當。
我們可以在@Component註解中為bean命名,如果採取預設ID的話,Spring會根據類名指定一個ID,具體規則就是將類名的第一個字母變成小寫。如果需要自定義ID的話,可以採取下面這樣的方式配置註解:
import org.springframework.stereotype.Component;
@Component("happyBean")
public class SgtPeppers implements CompactDisc {
}
不過需要注意的是:元件掃描預設是不啟用的。我們需要顯式配置一下Spring,從而命令它去尋找所有帶有@Component註解的類,併為它建立bean。
下面這個CDPlayerConfig類展現瞭如何實現顯式配置Spring的元件掃描:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
在CDPlayerConfig類中沒有顯式宣告任何bean,但是它通過使用@ComponentScan註解,讓Spring啟用了元件掃描。如果沒有其他配置的話,Spring會預設掃描與配置類相同的包。
如果希望Spring不僅僅掃描預設的基礎包,那麼我們可以採用如下的方式進行配置:
@Configuration
@ComponentScan(basePackages = {"soundsystem","video"})
public class CDPlayerConfig {
}
但是採用這樣的方法是型別不安全的,如果需要重構程式碼的話,那麼所指定的基礎包就可能出現錯誤。除了將包設定為簡單的String之外,@Component還提供了另一種方法,那就是將其指定為包中所包含的類或者介面:
@Configuration
@ComponentScan(basePackageClasses = {CDPlayer.class, Video.class})
public class CDPlayerConfig {
}
雖然在上面的示例中使用的是元件類,但是我們應該考慮在包中建立一個用來進行掃描的空標記介面。通過標記介面的方式,依然能夠保持對重構友好的介面引用,同時能夠避免引用任何實際的應用程式程式碼。
下面我們使用測試類來測試CompactDisc是不是真的創建出來了:
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {
@Autowired
private CompactDisc cd;
@Test
public void cdShouldNotBeNull() {
assertNotNull(cd);
}
}
這個測試類自動建立了Spring應用上下文,註解@ContextConfiguration告訴其需要在CDPlayerConfig中載入配置。因為CDPlayerConfig類中包含了@ComponentScan,因此最終的應用上下文應該包含了CompactDisc bean。
這個測試類應該可以通過測試,並以測試成功的顏色顯示。
元件掃描之後就應該是自動裝配了。簡單來說,自動裝配就是讓Spring自動滿足bean依賴的一種方法,在滿足依賴的過程中,會在Spring應用上下文中尋找匹配某個bean需求的其他bean。
為了實現自動裝配,我們可以藉助Spring的@Autowired註解。
考慮下面這個示例,通過自動裝配,將一個CompactDisc注入到CDPlayer中:
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();
}
}
它實現的MediaPlayer介面:
public interface MediaPlayer {
void play();
}
@Autowired註解不僅可以用在構造器上,在其他方法上,Spring都會嘗試滿足方法引數上宣告的依賴。
如果沒有匹配的bean,那麼在建立應用上下文時,Spring會丟擲一個異常。
下面使用一個測試類來測試自動裝配是否成功:
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.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
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());
}
}
在最後的斷言中,paly()測試方法呼叫了CDPlayer的paly()方法,並且斷言它的行為和預期的一致。