本篇介紹Spring中裝配Bean,內容皆總結摘抄自《Spring實戰》,僅作筆記。
在Spring中,對象無需自己查找或創建與其相關聯的其他對象。容器負責將需要相互協作的對象引用賦予各個對象。創建應用對象之間協作關係的行爲稱爲裝配。這也是依賴注入的本質。
Spring提供了三種主要的裝配機制:
- XML中進行顯示配置;
- Java中進行顯示配置;
- 隱式的bean發現機制和自動裝配。
雖然現在基本上都使用的自動裝配,但我們還是介紹一下其他兩種方式增強理解。
通過XML裝配bean
在Spring剛出現的時候,XML是描述配置的主要方式。在使用XML爲Spring裝配bean之前,需要創建一個新的配置規範,即XML文件,並且以<beans>元素爲根。最簡單的Spring XML配置如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
<beans>是Spring配置文件的根元素。但僅僅是這樣這個文件還沒有什麼作用,我們還需要聲明bean。使用<bean>來聲明bean,可以按照如下方式聲明bean:
<bean id="quest" class="RescueDamelQuest"></bean>
其中class是這個bean的全限定類名,在將這個bean裝配到其他bean中的時候,可以使用id來裝配。例如將RescueDamelQuest bean裝配到DamselRescuingKnight bean中,就可以直接使用其id"quest"來裝配。
<bean id="knight" class="DamselRescuingKnight">
<constructor-arg ref="quest"/>
</bean>
<bean id="quest" class="RescueDamelQuest"></bean>
藉助構造器注入初始化bean
現在我們有DamselRescuingKnight類如下:
public class DamselRescuingKnight implements Knight{
private Quest quest;
public DamselRescuingKnight(Quest quest) {
this.quest = quest;
}
public void embartOnQuest(){
quest.embark();
}
}
其中Quest是一個接口,RescueDamelQuest類實現了Quest接口。我們現在把RescueDamelQuest bean注入到DamselRescuingKnight bean中,XML配置如下:
<bean id="knight" class="DamselRescuingKnight">
<constructor-arg ref="quest"/>
</bean>
<bean id="quest" class="RescueDamelQuest"></bean>
當Spring遇到這個<bean>元素時,會創建一個DamselRescuingKnight實例,<constructor-arg>元素會告知Spring要將一個id爲quest的bean引用傳遞到DamselRescuingKnight構造器中。
以上介紹的是將對象的引用裝配到依賴它們的其他對象中,有時候需要用一個字面量值來配置對象,這時我們只需要將<constructor-arg>元素中的ref屬性替換爲value屬性即可,該屬性即表明給定的值以字面量的形式注入到構造器中。
有時還需要裝配集合,例如我們想要注入一個String集合,XML可以配置如下:
<bean id="knight" class="DamselRescuingKnight">
<constructor-arg ref="quest"/>
<util:list>
<value>Janny</value>
<value>Danny</value>
<value>LiMing</value>
</util:list>
</bean>
其中<list>是<constructor-arg>的子元素,表明一個包含值的列表將會傳遞到構造器中,<value>元素用來指定列表中的每個元素。類似的,也可以使用<ref>來替代<value>,實現bean引用列表的裝配。
Setter方法注入
對於有些類可能只有默認的構造器,這時我們可以選擇使用Setter方法注入。先將類修改如下:
public class DamselRescuingKnight implements Knight{
private Quest quest;
public void embartOnQuest(){
quest.embark();
}
public Quest getQuest() {
return quest;
}
public void setQuest(Quest quest) {
this.quest = quest;
}
}
XML配置如下:
<bean id="knight" class="DamselRescuingKnight">
<property name="quest" ref="quest"/>
</bean>
<bean id="quest" class="RescueDamelQuest"></bean>
<property>元素爲屬性的Setter方法所提供的功能與<constructor-arg>元素爲構造器提供的功能是一樣的。在上面的XML中,它引用了id爲quest的bean,通過setQuest()方法將其注入到quest屬性中。
在Setter方法注入中同樣可以注入字面量,只需要將<property>元素的ref屬性替換爲value屬性即可。
通過Java代碼裝配bean
使用XML裝配過於繁瑣,尤其是在較大型項目中,XML文件可能會很大。使用JavaConfig可能是一種更好的解決方案。JavaConfig更爲強大、類型安全並且對重構友好。在概念上,它與業務邏輯和領域代碼不同,儘管它同樣使用Java代碼進行表述,但JavaConfig是配置代碼,不應該包含任何業務邏輯,JavaConfig也不應該侵入到業務邏輯代碼中。
創建JavaConfig類的關鍵在於爲其添加@Configuration註解,@Configuration註解表明這個類是一個配置類,該類應該包含在Spring應用上下文中如何創建bean的細節。
要在JavaConfig中聲明bean,需要編寫一個方法,這個方法會創建所需類型的實例,然後給這個方法添加@Bean註解。例如,下面的代碼聲明瞭Knight bean:
@Bean
public Knight knight(){
return new DamselRescuingKnight();
}
@Bean註解會告訴Spring這個方法將會返回一個對象,該對象要註冊爲Spring應用上下文中的bean,方法體中包含了最終產生bean實例的邏輯。
默認情況下,bean的id與帶有@Bean註解的方法名是一樣的,例如本例中bean的id爲knight,也可以使用name屬性爲其指定另一個名字,例如:
@Bean(name="knightBean")
public Knight knight(){
return new DamselRescuingKnight();
}
有時我們聲明的bean可能依賴於其他bean,要將他們裝配在一起可以使用如下方式:
@Bean
public Knight knight(){
return new DamselRescuingKnight(quest());
}
@Bean
public Quest quest(){
return new RescueDamelQuest();
}
knight()方法中並沒有使用默認構造器來構建實例,而是調用了需要傳入Quest對象的構造器。看起來好像Quest是通過調用quest()得到的,事實上因爲quest()方法上加了@Bean註解,Spring將會攔截所有對它的調用,並確保直接返回該方法鎖創建的bean,而不是每次都對其進行實際的調用。
例如可以再添加另一個Knight bean,與之前的Knight bean完全一樣,如下:
@Bean
public Knight anotherKnight(){
return new DamselRescuingKnight(quest());
}
默認情況下,Spring中的bean都是單例的,因此兩個Knight bean會得到相同的Quest實例。
自動化裝配bean
最便利的也是現在我們最常用的還是自動化配置,Spring從兩個角度來實現自動化裝配:
- 組件掃描(component scanning):Spring會自動發現應用上下文中所創建的bean。
- 自動裝配(autowiring):Spring自動滿足bean之間的依賴。
組件掃描和自動裝配組合在一起能夠將顯示配置降低到最少。
可以在要創建bean的類上使用@Component註解,該註解會告知Spring爲這個類創建bean。但組件掃描默認是不啓用的,因此需要顯式配置一下Spring,從而命令它去尋找帶有@Component註解的類併爲其創建bean。如下配置類可以是完成這項任務的最簡潔配置。
@Configuration
@ComponentScan
public class KnightConfig {
}
KnightConfig類沒有顯示聲明任何bean,但使用了@ComponentScan註解,這個註解能夠在Spring中啓用組件掃描。如果沒有其他配置,@ComponentScan默認會掃描與配置類相同的包以及這個包下的所有子包,查找帶有Component註解的類。還可以使用name屬性爲組件設置id,其方式與JavaConfig中的@Bean類似。
@ComponentScan默認會以配置類所在的包作爲基礎包來掃描組件,但有時我們可能想要掃描不同的包,這時我們可以使用value屬性指定包的名稱,例如:
@ComponentScan("packageName")
如果想更加清晰的表明所設置的是基礎包,可以通過basePackages屬性進行配置:
@ComponentScan(basePackages="packageName")
這個屬性還可以設置多個基礎包,只需要將basePackages屬性設置爲要掃描包的一個數組即可:
@ComponentScan(basePackages={"packageA","packageB"})
自動裝配
自動裝配就是讓Spring自動滿足bean依賴的一種方法,在滿足依賴的過程中,會在Spring應用上下文中尋找匹配某個bean需求的其他bean。爲了聲明要進行自動裝配,我們可以使用Spring中的@Autowired註解。
@Autowired註解可以使用在構造函數、Setter方法和類成員變量上。例如:
@Component
public class DamselRescuingKnight implements Knight{
private Quest quest;
@Autowired
public DamselRescuingKnight(Quest quest) {
this.quest = quest;
}
public void embartOnQuest(){
quest.embark();
}
}
構造器上添加了@Autowired註解,表明當Spring創建DamselRescuingKnight bean的時候,會通過這個構造器來進行實例化並且會傳入一個可設置給Quest類型的bean。
在Setter方法上使用@Autowired註解如下:
@Component
public class DamselRescuingKnight implements Knight{
private Quest quest;
public void embartOnQuest(){
quest.embark();
}
public Quest getQuest() {
return quest;
}
@Autowired
public void setQuest(Quest quest) {
this.quest = quest;
}
}
事實上,Setter方法沒有特殊之處,@Autowired註解可以用在類的任何方法上,只要這個方法可以與Setter方法發揮的作用相同即可。