Spring學習筆記之自動化裝配Bean
阿新 • • 發佈:2019-01-07
在Spring中可以使用Java程式碼、XML和自動化裝配三種方式來裝配Bean。從便利性角度來說,最強大的還是Spring的自動化配置,如果Spring能夠進行自動化裝配的話,那何苦還要顯式的將這些Bean裝配在一起呢? Spring從兩個角度來實現自動化裝配: 元件掃描:Spring會自動發現應用上下文中所建立的Bean; 自動裝配:Spring自動滿足bean之間的依賴。 為了闡述元件掃描和裝配,我們需要建立幾個Bean,它們代表了一個音響系統中的元件。
一、建立可被發現的bean
定義CD的一個介面:CompactDisc介面定義了CD播放器對一盤CD所能進行的操作。它將CD播放器的任意實現與CD本身的耦合降低到了最小的程度。下面建立一個CompactDisc的實現:package cn.javacodes.spring.beans.soundsystem; public interface CompactDisc { void play(); }
這裡需要注意的是該類使用了@Component註解,表明該類會作為元件類,並告知Spring要為這個元件建立bean。但是在這之前,由於預設元件掃描是不啟用的。我們還需要顯式配置一下Spring,從而命令它去尋找帶有@Component註解的類,併為其建立bean。下面的這個類展現了完成這件事情的最簡介配置方式:package cn.javacodes.spring.beans.soundsystem; import org.springframework.stereotype.Component; @Component public class Transfer implements CompactDisc { private String title = "transfer"; private String artist = "周傳雄/小剛"; public void play() { System.out.println("正在播放"+artist+"的專輯:" + title); } }
package cn.javacodes.spring.beans.soundsystem; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan public class CDPlayerConfig { }
如果沒有其他配置的話,@ComponentScan預設會掃描與配置類相同的包。因為CDPlayerConfig類位於cn.javacodes.spring.beans.soundsystem包中,因此Spring將會掃描這個包以及這個包下的所有子包,查詢帶有@Component註解標示的類,並在Spring中自動為其建立一個bean。當然,如果你更加傾向於使用XML來啟用元件掃描的話,那麼可以使用Spring context名稱空間的<context:component-scan>元素。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:configurator="http://www.springframework.org/schema/c" xmlns:avalon="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="cn.javacodes.spring.beans.soundsystem"/> </beans>
儘管我們可以使用XML的方案來啟用元件掃描,但在後面的討論中,更多的還是會使用基於Java的配置。下面我們建立一個簡單的JUnit測試,它會建立Spring上下文,並判斷CompactDisc是不是真的創建出來了。
package cn.javacodes.spring.beans.soundsystem; 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; import static org.junit.Assert.assertNotNull; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = CDPlayerConfig.class) public class CDPlayerTest { @Autowired private CompactDisc cd; @Test public void cdShouldNotBeNull(){ assertNotNull(cd); } }
該類使用了Spring的SpringJUnit4ClassRunner,以便在測試開始的時候自動建立Spring的上下文。註解@ContextConfiguration會告訴它需要在CDPlayerConfig中載入配置。因為CDPlayerConfig類中包含了@ComponentScan註解,因此最終的應用上下文中應該包含CompactDisc bean。為了證明這一點,在測試程式碼中有一個CompactDisc屬性,並且這個屬性帶有@Autowired註解,以便於將CompactDisc bean注入到測試程式碼中,有關與@Autowired註解的更多內容將在後面講述。最後,有一個簡單的測試方法斷言cd屬性不為null。如果它不為null的話,就意味著Spring能夠發現CompactDisc類,自動在Spring上下文中將其建立為bean並將其注入到了測試程式碼中。這個程式碼應該能夠通過測試,並以測試成功的顏色顯示。
二、為元件掃描的bean命名
Spring上下文中所有的bean都有一個id。在前面的例子中,即使我們並沒有明確的給定Transfer bean一個id,但Spring會根據類名為其給定一個id。具體來講,Spring會預設給定一個將類名首字母變為小寫的id,例如上例中將給定的id為transfer。如果想為這個bean給定不同的id,你需要做的就是將你所想要給定的id作為引數傳遞給@Component註解。例如:package cn.javacodes.spring.beans.soundsystem; import org.springframework.stereotype.Component; @Component("transfer") public class Transfer implements CompactDisc { private String title = "transfer"; private String artist = "周傳雄/小剛"; public void play() { System.out.println("正在播放"+artist+"的專輯:" + title); } }
還有另外一種為bean命名的方式,使用Java依賴注入規範中提供的@Named註解來為bean設定id:
package cn.javacodes.spring.beans.soundsystem; import javax.inject.Named; @Named("transfer") public class Transfer implements CompactDisc { private String title = "transfer"; private String artist = "周傳雄/小剛"; public void play() { System.out.println("正在播放"+artist+"的專輯:" + title); } }
Spring支援將@Named作為@Component註解的替代方案。兩者之間有一些細微的差別,不過大多數場景種它們使可以相互替換的。但是推薦使用@Component而不是@Named,因為@Component註解看起來更加能夠知道它是幹什麼的。
三、設定元件掃描的基礎包
現在我們已經知道,預設情況下@ComponentScan註解會掃描當前配置類所在的包及其子包,但我們可能更希望將配置類與其它類放在不同的包中,那麼為了指定不同的基礎包,可以將指定的包名作為引數傳遞給@ComponentScan註解即可:@Configuration @ComponentScan("cn.javacodes.spring.beans.soundsystem") public class CDPlayerConfig { }
當然也可以更加清晰的指明其是基礎包,使用basePackages屬性:
@Configuration @ComponentScan(basePackages = "cn.javacodes.spring.beans.soundsystem") public class CDPlayerConfig { }
這裡我們發現basePackages屬性是複數形式,我們猜測它是否可以指定多個基礎包呢?答案是正確的,如果想要指定多個包,那麼只需要將要掃描的包放到一個數組中即可:
@Configuration @ComponentScan(basePackages = {"cn.javacodes.spring.beans.soundsystem", "cn.javacodes.spring.beans.video"}) public class CDPlayerConfig { }
上面的方式中,包名以簡單的字串進行表示,當然這是可以的。但是如果我們日後對程式碼進行重構,很有可能就會出現問題,所以這種通過簡單的字串來配置基礎包的方式是不安全的。為了解決這個問題,我們可以將其指定為包中所包含的類或介面:注意:這裡不再使用basePackages屬性,取而代之的是basePackageClasses屬性。我們不再使用String型別的包名來指定包,而是為basePackageClasses屬性設定的陣列中包含了類。這些類所在的包會作為元件掃描的基礎包。當然,使用元件類直接給basePackageClasses屬性並不是很好的方式,我們可以考慮在包中建立一個用來進行掃描的空標記介面。通過標記介面的方式,你依然能夠保持對重構友好的介面引用,但是可以避免引用任何實際的應用程式程式碼。
四、通過為bean添加註解實現自動裝配
簡單來說,自動裝配就是讓Spring自動滿足bean依賴的一種方法,在滿足依賴的過程中,會在Spring應用上下文中尋找匹配某個bean需求的其它bean。為了宣告要進行自動裝配,我們可以考慮使用Spring的@Autowired註解。比如下面的CDPlayer類,它的構造器使用了@Autowired註解,表明當Spring建立CDPlayer bean的時候,會通過這個構造器來進行例項化並會傳入一個可以設定給CompactDisc型別的bean:package cn.javacodes.spring.beans.soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CDPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play(){
cd.play();
}
}
@Autowired屬性不僅可以用在構造器上,還可以用在屬性的Setter方法上。比如說,如果CDPlayer有一個setCompactDisc()方法,那麼可以採用下面的方式來進行自動裝配:
@Autowired public void setCompactDisc(CompactDisc cd){ this.cd = cd; }
在Spring完成初始化bean之後,它會盡可能的去滿足bean的依賴。實際上,Setter方法並沒有什麼特殊之處,@Autowired可以出現在任何方法上。假如有且只有一個bean匹配依賴需求的話,那麼這個bean將會被封裝起來。如果沒有匹配的bean,那麼在應用上下文建立的時候,Spring將會丟擲一個異常。為了避免異常,可以將@Autowired的required屬性設定為false,Spring會嘗試執行自動匹配,但是如果沒有匹配的bean的話,Spring會讓這個bean處於未裝配的狀態:
@Autowired(required = false) public CDPlayer(CompactDisc cd) { this.cd = cd; }
但是,把required屬性設定為false的時候你需要注意,如果你的程式碼中沒有null檢查的話,這個處於未裝配狀態的屬性有可能會出現空指標異常(NullPointerException)。如果有多個bean都能滿足依賴關係的話,Spring會丟擲一個異常,表明沒有明確指定要選擇哪個bean進行裝配,有關於Spring自動化裝配的歧義性的問題,我會在後續的文章中進行說明。@Autowired是Spring特有的註解,如果你不希望在程式碼中到處使用Spring特有的註解的話,那麼可以考慮使用@Inject註解對其進行替換,例如:
package cn.javacodes.spring.beans.soundsystem; import javax.inject.Inject; import javax.inject.Named; @Named public class CDPlayer { private CompactDisc cd; @Inject public CDPlayer(CompactDisc cd) { this.cd = cd; } public void play(){ cd.play(); } }
@Inject註解來源於Java依賴注入規範,同@Named註解一樣,@Inject註解與@Autowired註解存在一些細微的差別,但大多數情況下它們可以進行相互替換。
五、驗證自動裝配
我們修改一下測試類CDPlayerTest,使其能夠藉助CDPlayer bean播放CD:package cn.javacodes.spring.beans.soundsystem; import cn.javacodes.spring.beans.MediaPlayer; import cn.javacodes.spring.configuration.CDPlayerConfig; 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 player; @Autowired private CompactDisc cd; @Test public void cdShouldNotBeNull() { assertNotNull(cd); } @Test public void play() { player.play(); assertEquals("正在播放周傳雄/小剛的專輯:transfern", log.getLog()); } }
該類中,除了注入CompactDisc,還將CDPlayer bean注入到了測試程式碼中(更為通用的MediaPlayer型別)。在play()方法中,我們可以呼叫CDPlayer的play()方法並斷言它的行為與你的預期是否一致。自動化裝配Bean還有更多的細節,我會在後續的文章中進行闡述。
本文為博主獨立部落格(https://javacodes.cn)同步發表,轉載請註明出處。
檢視原文:https://javacodes.cn/327.htm