Spring從兩個角度來實現自動化裝配:
- 組件掃描(component scanning):Spring會自動發現應用上下文中需要創建的bean。
- 自動裝配(autowiring):Spring會自動滿足bean之間的依賴。
為了更形象的解釋組件掃描與自動裝配,我們舉一個音響系統的例子,主要包含以下內容:
- CD接口
- CD接口的一個實現類
- CD播放器
關於CD和CD播放器關系的解釋:
如果你不將CD插入(注入)到CD播放器中,那么CD播放器其實是沒有太大用處的。所以,可以這樣說,
CD播放器依賴於CD才能完成它的使命。
1. 創建可被發現的bean
先創建CD接口CompactDisc:
package chapter02;
public interface CompactDisc {
void play();
}
然后創建CD接口的一個實現類SgtPeppers:
package chapter02;
import org.springframework.stereotype.Component;
@Component
public class SgtPeppers implements CompactDisc {
@Override
public void play() {
String title = "Sgt.Pepper's Lonely Hearts Club Band";
String artists = "The Beatles";
System.out.println("Playing " + title + " By " + artists);
}
}
SgtPeppers類與以往類的區別在於使用了@Component
注解。
這個注解表明該類會作為組件類,並告知Spring要為這個類創建bean。
那么如何讓Spring發現它並創建bean呢?
這時就需要用到組件掃描,不過,在Spring中,組件掃描默認是不啟用的。
因此我們需要顯式配置一下Spring,從而命令它去尋找帶有@Component注解的類,並為其創建bean。
創建CDPlayerConfig類:
package chapter02;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class CDPlayerConfig {
}
這個類與以往類的區別是使用了@ComponentScan
注解,這個注解能夠讓Spring啟用組件掃描。
@ComponentScan默認會掃描與配置類相同的包以及這個包下的所有子包,查找帶有@Component注解的類。
2. 驗證組件掃描
為了驗證創建的bean能否被Spring發現,我們創建一個簡單的JUnit測試,完成此測試需要導入以下兩個jar包:
- hamcrest-core-2.1.jar
- junit-4.12.jar
導入jar包的方式如下:
導入完成后的項目結構圖如下所示:
package chapter02;
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 compactDisc;
@Test
public void cdShouldNotBeNull() {
assertNotNull(compactDisc);
compactDisc.play();
}
}
代碼簡單講解:
@RunWith(SpringJUnit4ClassRunner.class)
,會在測試開始的時候自動創建Spring的應用上下文。
@ContextConfiguration(classes = CDPlayerConfig.class)
會告訴Spring需要在CDPlayerConfig中加載配置。
字段compactDisc上的@Autowired
注解,會將SgtPeppers bean注入到字段compactDisc上,因為它是接口CompactDisc的實現類並且添加了@Component
注解。
運行測試方法cdShouldNotBeNull,會發現測試通過,compactDisc不為null:
3. 設置bean ID
Spring應用上下文中所有的bean都會給定一個ID,默認情況下,Spring會將類名的第一個字母變為小寫,作為該bean的ID。
如上面代碼中SgtPeppers bean的ID為sgtPeppers。
有以下兩種方式來設置bean ID:
3.1 使用@Component設置bean ID
@Component("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
......
}
3.2 使用@Named設置bean ID
@Named注解不是Spring框架的注解,而是Java 依賴注入規范(Java Dependency Injection)中的注解,因此需要導入jar包:javax.inject-1.jar,導入jar包的方式可以參考 Spring入門(一):創建Spring項目。
import javax.inject.Named;
@Named("lonelyHeartsClub")
public class SgtPeppers implements CompactDisc {
......
}
在Spring項目中建議使用@Component
注解。
4. 設置組件掃描的基礎包
按照默認規則 ,@ComponentScan
注解會以配置類所在的包作為基礎包(base package)來掃描組件。
但有時候,我們會將配置類放在單獨的包中,使其與其他的應用代碼區分開來。
這種場景下,默認的基礎包就滿足不了需求。
@ComponentScan
注解支持傳入指定的基礎包,有以下幾種場景:
4.1 指定要掃描的基礎包(單個)
@ComponentScan("chapter02")
public class CDPlayerConfig {
}
或者:
@ComponentScan(basePackages = "chapter02")
public class CDPlayerConfig {
}
4.2 指定要掃描的基礎包(多個)
@ComponentScan(basePackages = {"chapter01", "chapter02"})
public class CDPlayerConfig {
}
4.3 指定要掃描的基礎包(類型安全)
@ComponentScan(basePackageClasses = {CDPlayer.class})
public class CDPlayerConfig {
}
basePackageClasses也支持指定多個類,指定類所在的包將會作為組件掃描的基礎包。
建議使用這種類型安全方式來指定掃描的基礎包。
5. 通過為bean添加注解實現自動裝配
自動裝配是讓Spring自動滿足bean依賴的一種方法,在滿足依賴的過程中,會在Spring應用上下文中尋找匹配某個bean需要的其他bean。
實現自動裝配,需要使用Spring的@Autowired
注解。
@Autowired
一般情況下,有以下3種使用方式:
5.1 使用在構造器上
package chapter02;
public interface MediaPlayer {
void play();
}
package chapter02;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
@Autowired
public CDPlayer(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
@Override
public void play() {
compactDisc.play();
}
}
5.2 使用在屬性的Setter方法上
package chapter02;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
@Autowired
public void setCompactDisc(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
@Override
public void play() {
compactDisc.play();
}
}
5.3 使用在類的任何方法上
package chapter02;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
@Autowired
public void insertDisc(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
@Override
public void play() {
compactDisc.play();
}
}
不管是構造器、Setter方法還是其他的方法,Spring都會嘗試滿足方法參數上所聲明的依賴。
假如有且只有一個bean匹配依賴需求的話,那么這個bean將會被裝配進來。
如果沒有匹配的bean,那么在應用上下文創建的時候,Spring會拋出一個異常。
可以通過設置require屬性為false避免該異常出現:
@Autowired(required = false)
public CDPlayer(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
不過建議謹慎使用,避免未找到bean進行匹配,而且代碼沒有進行null檢查而出現NullPointerException。
如果有多個bean都能滿足依賴關系的話,Spring將會拋出一個異常,表明沒有明確指定要選擇哪個bean進行自動裝配。
@Autowired注解也可以替換成@Inject注解(來源於Java依賴注入規范),同樣可以實現自動裝配:
package chapter02;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
@Inject
public CDPlayer(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
@Override
public void play() {
compactDisc.play();
}
}
在Spring項目中建議使用@Autowired注解。
6. 驗證自動裝配
修改CDPlayerTest類代碼測試自動裝配。
package chapter02;
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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
@Rule
public final StandardOutputStreamLog log = new StandardOutputStreamLog();
@Autowired
private MediaPlayer mediaPlayer;
@Autowired
private CompactDisc compactDisc;
@Test
public void cdShouldNotBeNull() {
assertNotNull(compactDisc);
compactDisc.play();
}
@Test
public void play() {
mediaPlayer.play();
assertEquals("Playing Sgt.Pepper's Lonely Hearts Club Band By The Beatles\r\n", log.getLog());
}
}
因為代碼中使用了StandardOutputStreamLog類,因此需要導入jar包:system-rules-1.16.0.jar,導入jar包的方式可以參考 Spring入門(一):創建Spring項目。
運行測試方法play(),輸出內容和預期一致,說明字段mediaPlayer已經被MediaPlayer的實現類CDPlayer bean裝配,測試通過,如下所示:
7. 源碼及參考
源碼地址:https://github.com/zwwhnly/spring-action.git,歡迎下載。
Craig Walls 《Spring實戰(第4版)》
原創不易,如果覺得文章能學到東西的話,歡迎點個贊、評個論、關個注,這是我堅持寫作的最大動力。
如果有興趣,歡迎添加我的微信:zwwhnly,等你來聊技術、職場、工作等話題(PS:我是一名奮斗在上海的程序員)。