1. 程式人生 > >【Spring學習筆記】7:裝配bean的三種方式(自動裝配,JavaConfig,XML),配置匯入

【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的三種方式

建立應用物件之間協作關係的行為稱為裝配

(wiring),在類中表現為組合,所以依賴的裝配也就是DI的目的。

[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,在之前實習的時候就發現公司系統裡也都是這樣做的。