【Spring學習筆記】7:裝配bean的三種方式(自動裝配,JavaConfig,XML),配置匯入
跟著《Spring實戰》徹底系統地學習一下Spring。
對Spring的新認識
Spring的關鍵
- 基於POJO的輕量級和最小侵入性程式設計。
- 通過DI和麵向介面實現鬆耦合。
- 基於切面和慣例進行宣告式程式設計。
- 通過切面和模板減少樣板式程式碼。
所謂侵入性,就是指很多框架要求使用者繼承它們的類或實現它們的介面,這樣應用程式就和框架繫結死了,Spring的侵入性很小。
注意,DI或者說IoC絕不是Spring框架的特權,Java就有一個JDI規範,很多有DI框架的功能都可以去實現它。
在單元測試時,可以直接用mock框架(如Mockito)去建立介面的mock實現,注入進去做測試。
Spring容器
Spring容器不止一個,Spring自帶了多個容器實現,常見的有bean工廠:
org.springframework.beans.factory.BeanFactory
該介面是最簡單的容器,提供基本的DI支援。
最廣泛使用的是應用上下文:
org.springframework.context.ApplicationContext
它基於bean工廠,提供了應用框架級別的服務。
ApplicationContext的幾種實現
不同的實現,主要區別在於裝載應用上下文的渠道不同。
- AnnotationConfigApplicationContext:從基於註解的配置類中載入上下文。
- AnnotationConfigWebApplicationContext:從基於註解的配置類中載入Spring Web上下文。
- ClassPathXmlApplicationContext:從類載入路徑下的xml配置檔案中載入上下文。
- FileSystemXmlApplicationContext:從檔案系統中的xml配置檔案載入上下文。
- XmlWebApplicationContext:從web應用下的xml配置檔案中載入Spring Web上下文。
其中第二個和第六個是和Spring的Web應用有關的,不在Spring核心容器的範疇內。
裝配bean的三種方式
建立應用物件之間協作關係的行為稱為裝配
[1]自動裝配
關鍵:元件掃描、自動裝配。
標識要被建立bean的類
給類加上@Component
註解,表示該類是一個元件類,Spring會為這個類建立bean,並將建立的bean(物件)納入Spring容器管理。
@Component
註解可以被JDI規範的@Named
註解代替,他們功能基本一樣。
元件類生成的bean預設id是類名第一個字母小寫,可以在註解中顯式指明id:
@Component(value = "lzh")
public class SbLzh {
//...
}
啟用元件掃描
[1]JavaConfig方式
使用@Configuration
註解,表示該類是一個配置類;再使用@ComponentScan
註解,預設掃描與該配置類相同的包及其子包,以發現元件類。
掃描某個包及其子包,將這”某個包”稱為基礎包。
如果想掃描其它包而不是預設的所在包,或者想指定多個掃描的基礎包,可以在@ComponentScan
註解上指定:
@ComponentScan(basePackages = {"org.lzh", "org.sb"})
這種方式是型別不安全的,因為這些基礎包是用String指定的,沒法在編譯期檢查其是否正確(存在)。可以用這種方式:
@ComponentScan(basePackageClasses = {LzhConfig.class, SbConfig.class})
這裡所指定的類或者介面所在的包將作為掃描的基礎包。不過程式中的類經常可能會變的,所以不妨額外設定空標記介面,僅僅用來標識所在包:
@ComponentScan(basePackageClasses = {LzhMark.class, SbMark.class})
[2]XML方式
在xml檔案中新增context名稱空間,使用:
<context:component-scan base-package="org.lzh"/>
開啟元件掃描,並指明要在哪個包及其子包下掃描元件類。
為bean添加註解實現自動裝配
使用Spring的@Autowired
或者JDI的@Inject
註解,新增到屬性上時,不必提供setter方法就會按型別尋找合適的bean自動注入;新增到方法上時,Spring都會嘗試滿足方法引數上所宣告的依賴,不論是什麼方法。
當沒有找到合適的bean來注入時,會丟擲異常,可以使用:
@Autowired(required = false)
說明這個自動注入不是必須的,如果找不到就算了(null)。
[2]通過JavaConfig裝配
JavaConfig是配置程式碼,不應該侵入到業務邏輯程式碼中,通常將JavaConfig放到單獨的包中。使用@Configuration
就建立了一個配置類。
宣告bean的方法
在方法上使用@Bean
註解來宣告一個bean:
//@Configuration指明是一個JavaConfig配置類
@Configuration
public class LzhConfig {
//@Bean修飾的方法用來宣告一個bean
@Bean
//返回值使用了抽象介面BookService(面向抽象),也可以面向具體
//方法名bkSrvc將成為bean的id
//方法內將對bean進行組裝,最後返回一個具體的實現類物件即組裝好的bean
public BookService bkSrvc() {
BookService bookService = new BookServiceImp();
//組裝這個bean...
//返回組裝好的bean物件
return bookService;
}
}
組裝bean的細節
前面那個例子裡的bean什麼依賴都沒有,如果一個bean依賴另一個bean,在裝配時最簡單的方式就是引用配置類中建立bean的方法,如:
@Configuration
public class LzhConfig {
@Bean
public Book book() {
return new Book();
}
@Bean
public BookService bkSrvc() {
//引用配置類中建立bean的方法
BookService bookService = new BookServiceImp(book());
return bookService;
}
}
注意這種方式和直接new一個傳進去的區別!Spring中的bean預設都是單例的,對於添加了@Bean
註解的方法,Spring會攔截所有對它的呼叫,並確保直接返回該方法所建立的單例bean,而不是每次都真的呼叫它!
另一種更好的方式是,從@Bean
方法的引數傳入bean:
@Bean
public BookService bkSrvc(Book bk) {
BookService bookService = new BookServiceImp(bk);
return bookService;
}
這種方式既能保證單例bean,又不要求該bean的宣告同在該配置類中。這個bean可以通過元件掃描自動發現,也可以通過XML來配置,也可以宣告在任何一個配置類中。
JavaConfig往往是最好的選擇
JavaConfig是裝配bean的最靈活的方式,因為它本質就是一個Java類,只受到Java語言的限制。Java畢竟是圖靈完備的,所以這種方式幾乎沒有功能上的限制。
通過XML裝配
這種方式已經用很多次了,只記錄一些新學到的知識。
bean的id
用<bean>
元素宣告一個bean,如果沒有給出id,那麼預設的id就是類名#計數
,如:
<bean class="org.lzh.model.Book"/>
<bean class="org.lzh.model.Book"/>
相當於:
<bean class="org.lzh.model.Book" id="org.lzh.model.Book#0"/>
<bean class="org.lzh.model.Book" id="org.lzh.model.Book#1"/>
在使用時為了簡潔,儘量只對那些需要將它ref注入到其它bean中的bean明確指明id。
使用合適的IDE
XML配置的一大缺點就是,像class這種屬性也只能用字串,所以XML配置不能從編譯期的型別檢查中受益,可以用能夠感知Spring功能的IDE(如STS和IDEA)來解決這個問題(編碼時就能檢查出來)。
c-名稱空間
Spring的c-
名稱空間可在一定程度上代替<constructor-arg>
為構造器注入bean,如:
<bean class="org.lzh.service.imp.BookServiceImp">
<constructor-arg ref="org.lzh.model.Book#0"/>
</bean>
可以替換為:
<bean class="org.lzh.service.imp.BookServiceImp" c:book-ref="org.lzh.model.Book#0"/>
其中,c:
後跟的book是構造器引數的名稱,-ref
表示是注入引用而不是字面量。如果不想使用構造器引數名稱,也可以使用引數的索引,即c:_索引號
配合-ref
:
<bean class="org.lzh.service.imp.BookServiceImp" c:_0-ref="org.lzh.model.Book#0"/>
裝配集合型別
當要把集合裝配到構造器引數中時,c-
名稱空間做不了,只能用<constructor-arg>
元素。
注意<set>
和<list>
用來裝配集合時沒什麼區別,都可以用來裝配List、Set和陣列。區別主要是在裝配到哪種集合型別裡,如果是Set那麼會忽略重複值。
<set>
和<list>
使用子元素<ref bean="..."/>
來實現引用集;使用<value>...</value>
(裡面的東西不需要雙引號)來實現字面量集。
p-名稱空間
Spring的p-
名稱空間可在一定程度上代替<property>
使用setter注入bean,如:
<bean class="org.lzh.service.imp.BookServiceImp">
<property name="book" ref="org.lzh.model.Book#0"/>
</bean>
可以替換為:
<bean class="org.lzh.service.imp.BookServiceImp" p:book-ref="org.lzh.model.Book#0"/>
當注入字面量時同樣是把-ref
去掉就行。
配置匯入
在前面學習使用引數注入到@Bean
修飾的方法中實現注入時,雖然不要求該bean的宣告同在該配置類中,但只要不是自動發現bean,就需要手動匯入配置,才能使用匯入的配置中所配置的bean。
因此,兩種手動配置的方式——JavaConfig和XML之間必定存在相互匯入的方式,下面逐個列舉。
JavaConfig中匯入JavaConfig
使用@Import
註解:
@Configuration
@Import(value = {SbConfig.class, CatConfig.class})
public class LzhConfig {
@Bean
public BookService bkSrvc(Book bk) { //這裡使用SbConfig中配置的Book類的bean
BookService bookService = new BookServiceImp(bk);
return bookService;
}
}
JavaConfig中匯入XML
使用@ImportResource
註解:
@Configuration
@ImportResource(value = {"classpath:CatContext.xml", "classpath:SbContext.xml"})
public class LzhConfig {
@Bean
public BookService bkSrvc(Book bk) { //這裡使用CatContext.xml中配置的Book類的bean
BookService bookService = new BookServiceImp(bk);
return bookService;
}
}
XML中匯入XML
使用<import>
元素:
<import resource="classpath:CatContext.xml"/>
XML中匯入JavaConfig
使用<bean>
元素:
<bean class="org.cat.CatConfig"/>
不管使用哪種手動配置方式,不管如何導來導去,通常都應建立一個根配置,匯入所有的JavaConfig和XML,在之前實習的時候就發現公司系統裡也都是這樣做的。