詳解 Spring 3.0 基於 Annotation 的依賴注入實現

http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-iocannt/index.html?ca=drs-tp4608


使用 @Repository、@Service、@Controller 和 @Component 將類標識爲 Bean

Spring 自 2.0 版本開始,陸續引入了一些註解用於簡化 Spring 的開發。@Repository 註解便屬於最先引入的一批,它用於將數據訪問層 (DAO 層 ) 的類標識爲 Spring Bean。具體只需將該註解標註在 DAO 類上即可。同時,爲了讓 Spring 能夠掃描類路徑中的類並識別出 @Repository 註解,需要在 XML 配置文件中啓用 Bean 的自動掃描功能,這可以通過 <context:component-scan/> 實現。如下所示:

 // 首先使用 @Repository 將 DAO 類聲明爲 Bean 
 package bookstore.dao; 
 @Repository 
 public class UserDaoImpl implements UserDao{ …… } 

 // 其次,在 XML 配置文件中啓動 Spring 的自動掃描功能
 <beans … > 
    ……
 <context:component-scan base-package=”bookstore.dao” /> 
……
 </beans> 

如此,我們就不再需要在 XML 中顯式使用 <bean/> 進行 Bean 的配置。Spring 在容器初始化時將自動掃描 base-package 指定的包及其子包下的所有 class 文件,所有標註了 @Repository 的類都將被註冊爲 Spring Bean。

爲什麼 @Repository 只能標註在 DAO 類上呢?這是因爲該註解的作用不只是將類識別爲 Bean,同時它還能將所標註的類中拋出的數據訪問異常封裝爲 Spring 的數據訪問異常類型。 Spring 本身提供了一個豐富的並且是與具體的數據訪問技術無關的數據訪問異常結構,用於封裝不同的持久層框架拋出的異常,使得異常獨立於底層的框架。

Spring 2.5 在 @Repository 的基礎上增加了功能類似的額外三個註解:@Component、@Service、@Constroller,它們分別用於軟件系統的不同層次:

  • @Component 是一個泛化的概念,僅僅表示一個組件 (Bean) ,可以作用在任何層次。
  • @Service 通常作用在業務層,但是目前該功能與 @Component 相同。
  • @Constroller 通常作用在控制層,但是目前該功能與 @Component 相同。

通過在類上使用 @Repository、@Component、@Service 和 @Constroller 註解,Spring 會自動創建相應的 BeanDefinition 對象,並註冊到 ApplicationContext 中。這些類就成了 Spring 受管組件。這三個註解除了作用於不同軟件層次的類,其使用方式與 @Repository 是完全相同的。

另外,除了上面的四個註解外,用戶可以創建自定義的註解,然後在註解上標註 @Component,那麼,該自定義註解便具有了與所 @Component 相同的功能。不過這個功能並不常用。

當一個 Bean 被自動檢測到時,會根據那個掃描器的 BeanNameGenerator 策略生成它的 bean 名稱。默認情況下,對於包含 name 屬性的 @Component、@Repository、 @Service 和 @Controller,會把 name 取值作爲 Bean 的名字。如果這個註解不包含 name 值或是其他被自定義過濾器發現的組件,默認 Bean 名稱會是小寫開頭的非限定類名。如果你不想使用默認 bean 命名策略,可以提供一個自定義的命名策略。首先實現 BeanNameGenerator 接口,確認包含了一個默認的無參數構造方法。然後在配置掃描器時提供一個全限定類名,如下所示:

 <beans ...> 
 <context:component-scan 
    base-package="a.b" name-generator="a.SimpleNameGenerator"/> 
 </beans> 

與通過 XML 配置的 Spring Bean 一樣,通過上述註解標識的 Bean,其默認作用域是"singleton",爲了配合這四個註解,在標註 Bean 的同時能夠指定 Bean 的作用域,Spring 2.5 引入了 @Scope 註解。使用該註解時只需提供作用域的名稱就行了,如下所示:

 @Scope("prototype") 
 @Repository 
 public class Demo { … } 

如果你想提供一個自定義的作用域解析策略而不使用基於註解的方法,只需實現 ScopeMetadataResolver 接口,確認包含一個默認的沒有參數的構造方法。然後在配置掃描器時提供全限定類名:

 <context:component-scan base-package="a.b"
 scope-resolver="footmark.SimpleScopeResolver" /> 

使用 @PostConstruct 和 @PreDestroy 指定生命週期回調方法

Spring Bean 是受 Spring IoC 容器管理,由容器進行初始化和銷燬的(prototype 類型由容器初始化之後便不受容器管理),通常我們不需要關注容器對 Bean 的初始化和銷燬操作,由 Spring 經過構造函數或者工廠方法創建的 Bean 就是已經初始化完成並立即可用的。然而在某些情況下,可能需要我們手工做一些額外的初始化或者銷燬操作,這通常是針對一些資源的獲取和釋放操作。Spring 1.x 爲此提供了兩種方式供用戶指定執行生命週期回調的方法。

第一種方式是實現 Spring 提供的兩個接口:InitializingBean 和 DisposableBean。如果希望在 Bean 初始化完成之後執行一些自定義操作,則可以讓 Bean 實現 InitializingBean 接口,該接口包含一個 afterPropertiesSet() 方法,容器在爲該 Bean 設置了屬性之後,將自動調用該方法;如果 Bean 實現了 DisposableBean 接口,則容器在銷燬該 Bean 之前,將調用該接口的 destroy() 方法。這種方式的缺點是,讓 Bean 類實現 Spring 提供的接口,增加了代碼與 Spring 框架的耦合度,因此不推薦使用。

第二種方式是在 XML 文件中使用 <bean> 的 init-method 和 destroy-method 屬性指定初始化之後和銷燬之前的回調方法,代碼無需實現任何接口。這兩個屬性的取值是相應 Bean 類中的初始化和銷燬方法,方法名任意,但是方法不能有參數。示例如下:

 <bean id=”userService” 
 class=”bookstore.service.UserService” 
 init-method=”init” destroy-method=”destroy”> 
    …
 </bean> 

Spring 2.5 在保留以上兩種方式的基礎上,提供了對 JSR-250 的支持。JSR-250 規範定義了兩個用於指定聲明週期方法的註解:@PostConstruct 和 @PreDestroy。這兩個註解使用非常簡單,只需分別將他們標註於初始化之後執行的回調方法或者銷燬之前執行的回調方法上。由於使用了註解,因此需要配置相應的 Bean 後處理器,亦即在 XML 中增加如下一行:

 <context:annotation-config /> 

比較上述三種指定生命週期回調方法的方式,第一種是不建議使用的,不但其用法不如後兩種方式靈活,而且無形中增加了代碼與框架的耦合度。後面兩種方式開發者可以根據使用習慣選擇其中一種,但是最好不要混合使用,以免增加維護的難度。

使用 @Required 進行 Bean 的依賴檢查

依賴檢查的作用是,判斷給定 Bean 的相應 Setter 方法是否都在實例化的時候被調用了。而不是判斷字段是否已經存在值了。Spring 進行依賴檢查時,只會判斷屬性是否使用了 Setter 注入。如果某個屬性沒有使用 Setter 注入,即使是通過構造函數已經爲該屬性注入了值,Spring 仍然認爲它沒有執行注入,從而拋出異常。另外,Spring 只管是否通過 Setter 執行了注入,而對注入的值卻沒有任何要求,即使注入的 <null/>,Spring 也認爲是執行了依賴注入。

<bean> 標籤提供了 dependency-check 屬性用於進行依賴檢查。該屬性的取值包括以下幾種:

  • none -- 默認不執行依賴檢查。可以在 <beans> 標籤上使用 default-dependency-check 屬性改變默認值。
  • simple -- 對原始基本類型和集合類型進行檢查。
  • objects -- 對複雜類型進行檢查(除了 simple 所檢查類型之外的其他類型)。
  • all -- 對所有類型進行檢查。

舊版本使用 dependency-check 在配置文件中設置,缺點是粒度較粗。使用 Spring2.0 提供的 @Required 註解,提供了更細粒度的控制。@Required 註解只能標註在 Setter 方法之上。因爲依賴注入的本質是檢查 Setter 方法是否被調用了,而不是真的去檢查屬性是否賦值了以及賦了什麼樣的值。如果將該註解標註在非 setXxxx() 類型的方法則被忽略。

爲了讓 Spring 能夠處理該註解,需要激活相應的 Bean 後處理器。要激活該後處理器,只需在 XML 中增加如下一行即可。

 <context:annotation-config/> 

當某個被標註了 @Required 的 Setter 方法沒有被調用,則 Spring 在解析的時候會拋出異常,以提醒開發者對相應屬性進行設置。

使用 @Resource、@Autowired 和 @Qualifier 指定 Bean 的自動裝配策略

自動裝配是指,Spring 在裝配 Bean 的時候,根據指定的自動裝配規則,將某個 Bean 所需要引用類型的 Bean 注入進來。<bean> 元素提供了一個指定自動裝配類型的 autowire 屬性,該屬性有如下選項:

  • no -- 顯式指定不使用自動裝配。
  • byName -- 如果存在一個和當前屬性名字一致的 Bean,則使用該 Bean 進行注入。如果名稱匹配但是類型不匹配,則拋出異常。如果沒有匹配的類型,則什麼也不做。
  • byType -- 如果存在一個和當前屬性類型一致的 Bean ( 相同類型或者子類型 ),則使用該 Bean 進行注入。byType 能夠識別工廠方法,即能夠識別 factory-method 的返回類型。如果存在多個類型一致的 Bean,則拋出異常。如果沒有匹配的類型,則什麼也不做。
  • constructor -- 與 byType 類似,只不過它是針對構造函數注入而言的。如果當前沒有與構造函數的參數類型匹配的 Bean,則拋出異常。使用該種裝配模式時,優先匹配參數最多的構造函數。
  • autodetect -- 根據 Bean 的自省機制決定採用 byType 還是 constructor 進行自動裝配。如果 Bean 提供了默認的構造函數,則採用 byType;否則採用 constructor 進行自動裝配。

當使用 byType 或者 constructor 類型的自動裝配的時候,自動裝配也支持引用類型的數組或者使用了泛型的集合,這樣,Spring 就會檢查容器中所有類型匹配的 Bean,組成集合或者數組後執行注入。對於使用了泛型的 Map 類型,如果鍵是 String 類型,則 Spring 也會自動執行裝配,將所有類型匹配的 Bean 作爲值,Bean 的名字作爲鍵。

我們可以給 <beans> 增加 default-autowire 屬性,設置默認的自動封裝策略。默認值爲"no"。如果使用自動裝配的同時,也指定了 property 或者 constructor-arg 標籤,則顯式指定的值將覆蓋自動裝配的值。目前的自動封裝不支持簡單類型,比如基本類型、String、Class,以及它們的數組類型。

在按類型匹配的時候 ( 可能是 byType、constructor、autodetect),同一個類型可能存在多個 Bean,如果被注入的屬性是數組、集合或者 Map,這可能沒有問題,但是如果只是簡單的引用類型,則會拋出異常。解決方法有如下幾種:

  • 取消該 Bean 的自動裝配特性,使用顯式的注入。我們可能不希望某個 Bean 被當作其他 Bean 執行自動封裝時的候選對象,我們可以給該 <bean> 增加 autowire-candidate="false"。(autowire-candidate 屬性和 autowire 屬性相互獨立,互不相干。某個 Bean 可以將 autowire-candidate 設置爲 false,同時使用 autowire 特性。) 另外,我們可以設置 <beans> 的 default-autowire-candidates 屬性,可以在該屬性中指定可以用於自動裝配候選 Bean 的匹配模式,比如 default-autowire-candidates="*serv,*dao",這表示所有名字以 serv 或者 dao 結尾的 Bean 被列爲候選,其他則忽略,相當於其他 Bean 都指定爲 autowire-candidate="false",此時可以顯式爲 <bean> 指定 autowire-candidate="true"。在 <bean> 上指定的設置要覆蓋 <beans> 上指定的設置。
  • 如果在多個類型相同的 Bean 中有首選的 Bean,那麼可以將該 <bean> 的 primary 屬性設置爲 "true" ,這樣自動裝配時便優先使用該 Bean 進行裝配。此時不能將 autowire-candidate 設爲 false。
  • 如果使用的是 Java 5 以上版本,可以使用註解進行更細粒度的控制。

使用 @Autowired 和 @Qualifier 註解執行自動裝配

使用 @Autowired 註解進行裝配,只能是根據類型進行匹配。@Autowired 註解可以用於 Setter 方法、構造函數、字段,甚至普通方法,前提是方法必須有至少一個參數。@Autowired 可以用於數組和使用泛型的集合類型。然後 Spring 會將容器中所有類型符合的 Bean 注入進來。@Autowired 標註作用於 Map 類型時,如果 Map 的 key 爲 String 類型,則 Spring 會將容器中所有類型符合 Map 的 value 對應的類型的 Bean 增加進來,用 Bean 的 id 或 name 作爲 Map 的 key。

@Autowired 標註作用於普通方法時,會產生一個副作用,就是在容器初始化該 Bean 實例的時候就會調用該方法。當然,前提是執行了自動裝配,對於不滿足裝配條件的情況,該方法也不會被執行。

當標註了 @Autowired 後,自動注入不能滿足,則會拋出異常。我們可以給 @Autowired 標註增加一個 required=false 屬性,以改變這個行爲。另外,每一個類中只能有一個構造函數的 @Autowired.required() 屬性爲 true。否則就出問題了。如果用 @Autowired 同時標註了多個構造函數,那麼,Spring 將採用貪心算法匹配構造函數 ( 構造函數最長 )。

@Autowired 還有一個作用就是,如果將其標註在 BeanFactory 類型、ApplicationContext 類型、ResourceLoader 類型、ApplicationEventPublisher 類型、MessageSource 類型上,那麼 Spring 會自動注入這些實現類的實例,不需要額外的操作。

當容器中存在多個 Bean 的類型與需要注入的相同時,注入將不能執行,我們可以給 @Autowired 增加一個候選值,做法是在 @Autowired 後面增加一個 @Qualifier 標註,提供一個 String 類型的值作爲候選的 Bean 的名字。舉例如下:

 @Autowired(required=false) 
 @Qualifier("ppp") 
 public void setPerson(person p){} 

@Qualifier 甚至可以作用於方法的參數 ( 對於方法只有一個參數的情況,我們可以將 @Qualifer 標註放置在方法聲明上面,但是推薦放置在參數前面 ),舉例如下:

 @Autowired(required=false) 
 public void sayHello(@Qualifier("ppp")Person p,String name){} 

我們可以在配置文件中指定某個 Bean 的 qualifier 名字,方法如下:

 <bean id="person" class="footmark.spring.Person"> 
    <qualifier value="ppp"/> 
 </bean> 

如果沒有明確指定 Bean 的 qualifier 名字,那麼默認名字就是 Bean 的名字。通常,qualifier 應該是有業務含義的,例如 "domain","persistent" 等,而不應該是類似 "person" 方式。

我們還可以將 @Qualifier 標註在集合類型上,那麼所有 qualifier 名字與指定值相同的 Bean 都將被注入進來。

最後,配置文件中需要指定每一個自定義註解的屬性值。我們可以使用 <meta> 標籤來代替 <qualifier/> 標籤,如果 <meta> 標籤和 <qualifier/> 標籤同時出現,那麼優先使用 <qualifier> 標籤。如果沒有 <qualifier> 標籤,那麼會用 <meta> 提供的鍵值對來封裝 <qualifier> 標籤。示例如下:

 <bean class="footmark.HelloWorld"> 
 <qualifier type="MovieQualifier"> 
 <attribute key="format" value="VHS"/> 
 <attribute key="genre" value="Comedy"/> 
 </qualifier> 
 </bean> 
 <bean class="footmark.HelloWorld"> 
 <meta key="format" value="DVD"/> 
 <meta key="genre" value="Action"/> 
 </bean> 

@Autowired 註解對應的後處理註冊與前面相似,只需在配置文件中增加如下一行即可:

 <context:annotation-config/> 

如果 @Autowired 注入的是 BeanFactory、ApplicationContext、ResourceLoader 等系統類型,那麼則不需要 @Qualifier,此時即使提供了 @Qualifier 註解,也將會被忽略;而對於自定義類型的自動裝配,如果使用了 @Qualifier 註解並且沒有名字與之匹配的 Bean,則自動裝配匹配失敗。

使用 JSR-250 中的 @Resource 和 @Qualifier 註解

如果希望根據 name 執行自動裝配,那麼應該使用 JSR-250 提供的 @Resource 註解,而不應該使用 @Autowired 與 @Qualifier 的組合。

@Resource 使用 byName 的方式執行自動封裝。@Resource 標註可以作用於帶一個參數的 Setter 方法、字段,以及帶一個參數的普通方法上。@Resource 註解有一個 name 屬性,用於指定 Bean 在配置文件中對應的名字。如果沒有指定 name 屬性,那麼默認值就是字段或者屬性的名字。@Resource 和 @Qualifier 的配合雖然仍然成立,但是 @Qualifier 對於 @Resource 而言,幾乎與 name 屬性等效。

如果 @Resource 沒有指定 name 屬性,那麼使用 byName 匹配失敗後,會退而使用 byType 繼續匹配,如果再失敗,則拋出異常。在沒有爲 @Resource 註解顯式指定 name 屬性的前提下,如果將其標註在 BeanFactory 類型、ApplicationContext 類型、ResourceLoader 類型、ApplicationEventPublisher 類型、MessageSource 類型上,那麼 Spring 會自動注入這些實現類的實例,不需要額外的操作。此時 name 屬性不需要指定 ( 或者指定爲""),否則注入失敗;如果使用了 @Qualifier,則該註解將被忽略。而對於用戶自定義類型的注入,@Qualifier 和 name 等價,並且不被忽略。

<bean> 的 primary 和 autowire-candidate 屬性對 @Resource、@Autowired 仍然有效。

使用 @Configuration 和 @Bean 進行 Bean 的聲明

雖然 2.0 版本發佈以來,Spring 陸續提供了十多個註解,但是提供的這些註解只是爲了在某些情況下簡化 XML 的配置,並非要取代 XML 配置方式。這一點可以從 Spring IoC 容器的初始化類可以看出:ApplicationContext 接口的最常用的實現類是 ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext,以及面向 Portlet 的 XmlPortletApplicationContext 和麪向 web 的 XmlWebApplicationContext,它們都是面向 XML 的。Spring 3.0 新增了另外兩個實現類:AnnotationConfigApplicationContext 和 AnnotationConfigWebApplicationContext。從名字便可以看出,它們是爲註解而生,直接依賴於註解作爲容器配置信息來源的 IoC 容器初始化類。由於 AnnotationConfigWebApplicationContext 是 AnnotationConfigApplicationContext 的 web 版本,其用法與後者相比幾乎沒有什麼差別,因此本文將以 AnnotationConfigApplicationContext 爲例進行講解。

AnnotationConfigApplicationContext 搭配上 @Configuration 和 @Bean 註解,自此,XML 配置方式不再是 Spring IoC 容器的唯一配置方式。兩者在一定範圍內存在着競爭的關係,但是它們在大多數情況下還是相互協作的關係,兩者的結合使得 Spring IoC 容器的配置更簡單,更強大。

之前,我們將配置信息集中寫在 XML 中,如今使用註解,配置信息的載體由 XML 文件轉移到了 Java 類中。我們通常將用於存放配置信息的類的類名以 “Config” 結尾,比如 AppDaoConfig.java、AppServiceConfig.java 等等。我們需要在用於指定配置信息的類上加上 @Configuration 註解,以明確指出該類是 Bean 配置的信息源。並且 Spring 對標註 Configuration 的類有如下要求:

  • 配置類不能是 final 的;
  • 配置類不能是本地化的,亦即不能將配置類定義在其他類的方法內部;
  • 配置類必須有一個無參構造函數。

AnnotationConfigApplicationContext 將配置類中標註了 @Bean 的方法的返回值識別爲 Spring Bean,並註冊到容器中,受 IoC 容器管理。@Bean 的作用等價於 XML 配置中的 <bean/> 標籤。示例如下:

 @Configuration 
 public class BookStoreDaoConfig{ 
    @Bean 
    public UserDao userDao(){ return new UserDaoImpl();} 
    @Bean 
    public BookDao bookDao(){return new BookDaoImpl();} 
 } 

Spring 在解析到以上文件時,將識別出標註 @Bean 的所有方法,執行之,並將方法的返回值 ( 這裏是 UserDaoImpl 和 BookDaoImpl 對象 ) 註冊到 IoC 容器中。默認情況下,Bean 的名字即爲方法名。因此,與以上配置等價的 XML 配置如下:

 <bean id=”userDao” class=”bookstore.dao.UserDaoImpl”/> 
 <bean id=”bookDao” class=”bookstore.dao.BookDaoImpl”/> 

@Bean 具有以下四個屬性:

  • name -- 指定一個或者多個 Bean 的名字。這等價於 XML 配置中 <bean> 的 name 屬性。
  • initMethod -- 容器在初始化完 Bean 之後,會調用該屬性指定的方法。這等價於 XML 配置中 <bean> 的 init-method 屬性。
  • destroyMethod -- 該屬性與 initMethod 功能相似,在容器銷燬 Bean 之前,會調用該屬性指定的方法。這等價於 XML 配置中 <bean> 的 destroy-method 屬性。
  • autowire -- 指定 Bean 屬性的自動裝配策略,取值是 Autowire 類型的三個靜態屬性。Autowire.BY_NAME,Autowire.BY_TYPE,Autowire.NO。與 XML 配置中的 autowire 屬性的取值相比,這裏少了 constructor,這是因爲 constructor 在這裏已經沒有意義了。

@Bean 沒有直接提供指定作用域的屬性,可以通過 @Scope 來實現該功能,關於 @Scope 的用法已在上文列舉。

下面講解基於註解的容器初始化。AnnotationConfigApplicationContext 提供了三個構造函數用於初始化容器。

  • AnnotationConfigApplicationContext():該構造函數初始化一個空容器,容器不包含任何 Bean 信息,需要在稍後通過調用其 register() 方法註冊配置類,並調用 refresh() 方法刷新容器。
  • AnnotationConfigApplicationContext(Class<?>... annotatedClasses):這是最常用的構造函數,通過將涉及到的配置類傳遞給該構造函數,以實現將相應配置類中的 Bean 自動註冊到容器中。
  • AnnotationConfigApplicationContext(String... basePackages):該構造函數會自動掃描以給定的包及其子包下的所有類,並自動識別所有的 Spring Bean,將其註冊到容器中。它不但識別標註 @Configuration 的配置類並正確解析,而且同樣能識別使用 @Repository、@Service、@Controller、@Component 標註的類。

除了使用上面第三種類型的構造函數讓容器自動掃描 Bean 的配置信息以外,AnnotationConfigApplicationContext 還提供了 scan() 方法,其功能與上面也類似,該方法主要用在容器初始化之後動態增加 Bean 至容器中。調用了該方法以後,通常需要立即手動調用 refresh() 刷新容器,以讓變更立即生效。

需要注意的是,AnnotationConfigApplicationContext 在解析配置類時,會將配置類自身註冊爲一個 Bean,因爲 @Configuration 註解本身定義時被 @Component 標註了。因此可以說,一個 @Configuration 同時也是一個 @Component。大多數情況下,開發者用不到該 Bean,並且在理想情況下,該 Bean 應該是對開發者透明的。@Configuration 的定義如下所示:

 @Target({ElementType.TYPE}) 
 @Retention(RetentionPolicy.RUNTIME) 
 @Documented 
 @Component 
 public @interface Configuration { 
 String value() default ""; 
 } 

在一般的項目中,爲了結構清晰,通常會根據軟件的模塊或者結構定義多個 XML 配置文件,然後再定義一個入口的配置文件,該文件使用 <import/> 將其他的配置文件組織起來。最後只需將該文件傳給 ClassPathXmlApplicationContext 的構造函數即可。針對基於註解的配置,Spring 也提供了類似的功能,只需定義一個入口配置類,並在該類上使用 @Import 註解引入其他的配置類即可,最後只需要將該入口類傳遞給 AnnotationConfigApplicationContext。具體示例如下:

 @Configuration 
 @Import({BookStoreServiceConfig.class,BookStoreDaoConfig.class}) 
 public class BookStoreConfig{ … } 

混合使用 XML 與註解進行 Bean 的配置

設計 @Configuration 和 @Bean 的初衷,並不是爲了完全取代 XML,而是爲了在 XML 之外多一種可行的選擇。由於 Spring 自發布以來,Spring 開發小組便不斷簡化 XML 配置,使得 XML 配置方式已經非常成熟,加上 Spring 2.0 以後出現了一系列命名空間的支持,使得 XML 配置方式成爲了使用簡單、功能強大的 Bean 定義方式。而且,XML 配置的一些高級功能目前還沒有相關注解能夠直接支持。因此,在目前的多數項目中,要麼使用純粹的 XML 配置方式進行 Bean 的配置,要麼使用以註解爲主,XML 爲輔的配置方式進行 Bean 的配置。

之所以會出現兩者共存的情況,主要歸結爲三個原因:其一,目前絕大多數採用 Spring 進行開發的項目,幾乎都是基於 XML 配置方式的,Spring 在引入註解的同時,必須保證註解能夠與 XML 和諧共存,這是前提;其二,由於註解引入較晚,因此功能也沒有發展多年的 XML 強大,因此,對於複雜的配置,註解還很難獨當一面,在一段時間內仍然需要 XML 的配合才能解決問題。除此之外,Spring 的 Bean 的配置方式與 Spring 核心模塊之間是解耦的,因此,改變配置方式對 Spring 的框架自身是透明的。Spring 可以通過使用 Bean 後處理器 (BeanPostProcessor) 非常方便的增加對於註解的支持。這在技術實現上非常容易的事情。

要使用混合配置方式,首先需要判斷以哪一種配置方式爲主。對這個問題的不同回答將會直接影響到實現的方式。然而大可不必爲此傷腦筋,因爲不論是以 XML 爲主,還是以註解爲主,配置方式都是簡單而且容易理解的。這裏不存在錯誤的決定,因爲僅僅是表現方式不一樣。我們首先假設以 XML 配置爲主的情況。

對於已經存在的大型項目,可能初期是以 XML 進行 Bean 配置的,後續逐漸加入了註解的支持,這時我們只需在 XML 配置文件中將被 @Configuration 標註的類定義爲普通的 <bean>,同時註冊處理註解的 Bean 後處理器即可。示例如下:

 // 假設存在如下的 @Configuration 類:
 package bookstore.config; 
 import bookstore.dao.*; 
 @Configuration 
 public class MyConfig{ 
 @Bean 
    public UserDao userDao(){ 
        return new UserDaoImpl(); 
    } 
 } 
此時,只需在 XML 中作如下聲明即可:
 <beans … > 
    ……
    <context:annotation-config /> 
    <bean class=”demo.config.MyConfig”/> 
 </beans> 

由於啓用了針對註解的 Bean 後處理器,因此在 ApplicationContext 解析到 MyConfig 類時,會發現該類標註了 @Configuration 註解,隨後便會處理該類中標註 @Bean 的方法,將這些方法的返回值註冊爲容器總的 Bean。

對於以上的方式,如果存在多個標註了 @Configuration 的類,則需要在 XML 文件中逐一列出。另一種方式是使用前面提到的自動掃描功能,配置如下:

 <context:component-scan base-package=”bookstore.config” /> 

如此,Spring 將掃描所有 demo.config 包及其子包中的類,識別所有標記了 @Component、@Controller、@Service、@Repository 註解的類,由於 @Configuration 註解本身也用 @Component 標註了,Spring 將能夠識別出 @Configuration 標註類並正確解析之。

對於以註解爲中心的配置方式,只需使用 @ImportResource 註解引入存在的 XML 即可,如下所示:

 @Configuration 
 @ImportResource(“classpath:/bookstore/config/spring-beans.xml”) 
 public class MyConfig{ 
……
 } 
 // 容器的初始化過程和純粹的以配置爲中心的方式一致:
 AnnotationConfigApplicationContext ctx = 
              new AnnotationConfigApplicationContext(MyConfig.class); 
……

結束語

從 2.0 版本開始,Spring 的每一次更新都會提供更多新的註解供開發者使用。這滿足了註解愛好者的胃口。但是正如前面所說,Spring 提供更多的註解並不是爲了有朝一日取代 XML 配置方式,而是爲了給開發者多一種選擇。兩種聲明 Bean 的方式各有特色,XML 方式更加靈活,並且發展的相對成熟,這種配置方式爲大多數 Spring 開發者熟悉;註解方式使用起來非常簡潔,但是尚處於發展階段。我們很難說兩種配置方式孰優孰劣,但是如果能夠靈活搭配兩種方式,一定能夠進一步提升開發效率。


參考資料



發佈了24 篇原創文章 · 獲贊 5 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章