概述
注釋配置相對于 XML 配置具有很多的優勢:
- 它可以充分利用 Java 的反射機制擷取類結構資訊,這些資訊可以有效減少配置的工作。如使用 JPA 注釋配置 ORM 映射時,我們就不需要指定 PO 的屬性名、類型等資訊,如果關系表字段和 PO 屬性名、類型都一緻,您甚至無需編寫任務屬性映射資訊——因為這些資訊都可以通過 Java 反射機制擷取。
- 注釋和 Java 代碼位于一個檔案中,而 XML 配置采用獨立的配置檔案,大多數配置資訊在程式開發完成後都不會調整,如果配置資訊和 Java 代碼放在一起,有助于增強程式的内聚性。而采用獨立的 XML 配置檔案,程式員在編寫一個功能時,往往需要在程式檔案和配置檔案中不停切換,這種思維上的不連貫會降低開發效率。
是以在很多情況下,注釋配置比 XML 配置更受歡迎,注釋配置有進一步流行的趨勢。Spring 2.5 的一大增強就是引入了很多注釋類,現在您已經可以使用注釋配置完成大部分 XML 配置的功能。在這篇文章裡,我們将向您講述使用注釋進行 Bean 定義和依賴注入的内容。
回頁首
原來我們是怎麼做的
在使用注釋配置之前,先來回顧一下傳統上是如何配置 Bean 并完成 Bean 之間依賴關系的建立。下面是 3 個類,它們分别是 Office、Car 和 Boss,這 3 個類需要在 Spring 容器中配置為 Bean:
Office 僅有一個屬性:
清單 1. Office.java
|
Car 擁有兩個屬性:
清單 2. Car.java
|
Boss 擁有 Office 和 Car 類型的兩個屬性:
清單 3. Boss.java
|
我們在 Spring 容器中将 Office 和 Car 聲明為 Bean,并注入到 Boss Bean 中:下面是使用傳統 XML 完成這個工作的配置檔案 beans.xml:
清單 4. beans.xml 将以上三個類配置成 Bean
|
當我們運作以下代碼時,控制台将正确打出 boss 的資訊:
清單 5. 測試類:AnnoIoCTest.java
|
這說明 Spring 容器已經正确完成了 Bean 建立和裝配的工作。
回頁首
使用 @Autowired 注釋
Spring 2.5 引入了
@Autowired
注釋,它可以對類成員變量、方法及構造函數進行标注,完成自動裝配的工作。來看一下使用
@Autowired
進行成員變量自動注入的代碼:
清單 6. 使用 @Autowired 注釋的 Boss.java
|
Spring 通過一個
BeanPostProcessor
對
@Autowired
進行解析,是以要讓
@Autowired
起作用必須事先在 Spring 容器中聲明
AutowiredAnnotationBeanPostProcessor
Bean。
清單 7. 讓 @Autowired 注釋工作起來
|
這樣,當 Spring 容器啟動時,
AutowiredAnnotationBeanPostProcessor
将掃描 Spring 容器中所有 Bean,當發現 Bean 中擁有
@Autowired
注釋時就找到和其比對(預設按類型比對)的 Bean,并注入到對應的地方中去。
按照上面的配置,Spring 将直接采用 Java 反射機制對 Boss 中的
car
和
office
這兩個私有成員變量進行自動注入。是以對成員變量使用
@Autowired
後,您大可将它們的 setter 方法(
setCar()
和
setOffice()
)從 Boss 中删除。
當然,您也可以通過
@Autowired
對方法或構造函數進行标注,來看下面的代碼:
清單 8. 将 @Autowired 注釋标注在 Setter 方法上
|
這時,
@Autowired
将查找被标注的方法的入參類型的 Bean,并調用方法自動注入這些 Bean。而下面的使用方法則對構造函數進行标注:
清單 9. 将 @Autowired 注釋标注在構造函數上
|
由于
Boss()
構造函數有兩個入參,分别是
car
和
office
,
@Autowired
将分别尋找和它們類型比對的 Bean,将它們作為
Boss(Car car ,Office office)
的入參來建立
Boss
Bean。
回頁首
當候選 Bean 數目不為 1 時的應對方法
在預設情況下使用
@Autowired
注釋進行自動注入時,Spring 容器中比對的候選 Bean 數目必須有且僅有一個。當找不到一個比對的 Bean 時,Spring 容器将抛出
BeanCreationException
異常,并指出必須至少擁有一個比對的 Bean。我們可以來做一個實驗:
清單 10. 候選 Bean 數目為 0 時
|
由于
office
Bean 被注釋掉了,是以 Spring 容器中将沒有類型為
Office
的 Bean 了,而 Boss 的
office
屬性标注了
@Autowired
,當啟動 Spring 容器時,異常就産生了。
當不能确定 Spring 容器中一定擁有某個類的 Bean 時,可以在需要自動注入該類 Bean 的地方可以使用
@Autowired(required = false)
,這等于告訴 Spring:在找不到比對 Bean 時也不報錯。來看一下具體的例子:
清單 11. 使用 @Autowired(required = false)
|
當然,一般情況下,使用
@Autowired
的地方都是需要注入 Bean 的,使用了自動注入而又允許不注入的情況一般僅會在開發期或測試期碰到(如為了快速啟動 Spring 容器,僅引入一些子產品的 Spring 配置檔案),是以
@Autowired(required = false)
會很少用到。
和找不到一個類型比對 Bean 相反的一個錯誤是:如果 Spring 容器中擁有多個候選 Bean,Spring 容器在啟動時也會抛出
BeanCreationException
異常。來看下面的例子:
清單 12. 在 beans.xml 中配置兩個 Office 類型的 Bean
|
我們在 Spring 容器中配置了兩個類型為
Office
類型的 Bean,當對 Boss 的
office
成員變量進行自動注入時,Spring 容器将無法确定到底要用哪一個 Bean,是以異常發生了。
Spring 允許我們通過
@Qualifier
注釋指定注入 Bean 的名稱,這樣歧義就消除了,可以通過下面的方法解決異常:
清單 13. 使用 @Qualifier 注釋指定注入 Bean 的名稱
|
@Qualifier("office")
中的
office
是 Bean 的名稱,是以
@Autowired
和
@Qualifier
結合使用時,自動注入的政策就從 byType 轉變成 byName 了。
@Autowired
可以對成員變量、方法以及構造函數進行注釋,而
@Qualifier
的标注對象是成員變量、方法入參、構造函數入參。正是由于注釋對象的不同,是以 Spring 不将
@Autowired
和
@Qualifier
統一成一個注釋類。下面是對成員變量和構造函數入參進行注釋的代碼:
對成員變量進行注釋:
清單 14. 對成員變量使用 @Qualifier 注釋
|
對構造函數入參進行注釋:
清單 15. 對構造函數變量使用 @Qualifier 注釋
|
@Qualifier
隻能和
@Autowired
結合使用,是對
@Autowired
有益的補充。一般來講,
@Qualifier
對方法簽名中入參進行注釋會降低代碼的可讀性,而對成員變量注釋則相對好一些。
回頁首
使用 JSR-250 的注釋
Spring 不但支援自己定義的
@Autowired
的注釋,還支援幾個由 JSR-250 規範定義的注釋,它們分别是
@Resource
、
@PostConstruct
以及
@PreDestroy
。
@Resource
@Resource
的作用相當于
@Autowired
,隻不過
@Autowired
按 byType 自動注入,面
@Resource
預設按 byName 自動注入罷了。
@Resource
有兩個屬性是比較重要的,分别是 name 和 type,Spring 将
@Resource
注釋的 name 屬性解析為 Bean 的名字,而 type 屬性則解析為 Bean 的類型。是以如果使用 name 屬性,則使用 byName 的自動注入政策,而使用 type 屬性時則使用 byType 自動注入政策。如果既不指定 name 也不指定 type 屬性,這時将通過反射機制使用 byName 自動注入政策。
Resource 注釋類位于 Spring 釋出包的 lib/j2ee/common-annotations.jar 類包中,是以在使用之前必須将其加入到項目的類庫中。來看一個使用
@Resource
的例子:
清單 16. 使用 @Resource 注釋的 Boss.java
|
一般情況下,我們無需使用類似于
@Resource(type=Car.class)
的注釋方式,因為 Bean 的類型資訊可以通過 Java 反射從代碼中擷取。
要讓 JSR-250 的注釋生效,除了在 Bean 類中标注這些注釋外,還需要在 Spring 容器中注冊一個負責處理這些注釋的
BeanPostProcessor
:
|
CommonAnnotationBeanPostProcessor
實作了
BeanPostProcessor
接口,它負責掃描使用了 JSR-250 注釋的 Bean,并對它們進行相應的操作。
@PostConstruct 和 @PreDestroy
Spring 容器中的 Bean 是有生命周期的,Spring 允許在 Bean 在初始化完成後以及 Bean 銷毀前執行特定的操作,您既可以通過實作 InitializingBean/DisposableBean 接口來定制初始化之後 / 銷毀之前的操作方法,也可以通過 <bean> 元素的 init-method/destroy-method 屬性指定初始化之後 / 銷毀之前調用的操作方法。關于 Spring 的生命周期,筆者在《精通 Spring 2.x—企業應用開發精解》第 3 章進行了詳細的描述,有興趣的讀者可以查閱。
JSR-250 為初始化之後/銷毀之前方法的指定定義了兩個注釋類,分别是 @PostConstruct 和 @PreDestroy,這兩個注釋隻能應用于方法上。标注了 @PostConstruct 注釋的方法将在類執行個體化後調用,而标注了 @PreDestroy 的方法将在類銷毀之前調用。
清單 17. 使用 @PostConstruct 和 @PreDestroy 注釋的 Boss.java
|
您隻需要在方法前标注
@PostConstruct
或
@PreDestroy
,這些方法就會在 Bean 初始化後或銷毀之前被 Spring 容器執行了。
我們知道,不管是通過實作
InitializingBean
/
DisposableBean
接口,還是通過 <bean> 元素的
init-method/destroy-method
屬性進行配置,都隻能為 Bean 指定一個初始化 / 銷毀的方法。但是使用
@PostConstruct
和
@PreDestroy
注釋卻可以指定多個初始化 / 銷毀方法,那些被标注
@PostConstruct
或
@PreDestroy
注釋的方法都會在初始化 / 銷毀時被執行。
通過以下的測試代碼,您将可以看到 Bean 的初始化 / 銷毀方法是如何被執行的:
清單 18. 測試類代碼
|
這時,您将看到标注了
@PostConstruct
的
postConstruct1()
方法将在 Spring 容器啟動時,建立
Boss
Bean 的時候被觸發執行,而标注了
@PreDestroy
注釋的
preDestroy1()
方法将在 Spring 容器關閉前銷毀
Boss
Bean 的時候被觸發執行。
回頁首
使用 <context:annotation-config/> 簡化配置
Spring 2.1 添加了一個新的 context 的 Schema 命名空間,該命名空間對注釋驅動、屬性檔案引入、加載期織入等功能提供了便捷的配置。我們知道注釋本身是不會做任何事情的,它僅提供中繼資料資訊。要使中繼資料資訊真正起作用,必須讓負責處理這些中繼資料的處理器工作起來。
而我們前面所介紹的
AutowiredAnnotationBeanPostProcessor
和
CommonAnnotationBeanPostProcessor
就是處理這些注釋中繼資料的處理器。但是直接在 Spring 配置檔案中定義這些 Bean 顯得比較笨拙。Spring 為我們提供了一種友善的注冊這些
BeanPostProcessor
的方式,這就是 <context:annotation-config/>。請看下面的配置:
清單 19. 調整 beans.xml 配置檔案
|
<context:annotationconfig/> 将隐式地向 Spring 容器注冊
AutowiredAnnotationBeanPostProcessor
、
CommonAnnotationBeanPostProcessor
、
PersistenceAnnotationBeanPostProcessor
以及
equiredAnnotationBeanPostProcessor
這 4 個 BeanPostProcessor。
在配置檔案中使用 context 命名空間之前,必須在 <beans> 元素中聲明 context 命名空間。
回頁首
使用 @Component
雖然我們可以通過
@Autowired
或
@Resource
在 Bean 類中使用自動注入功能,但是 Bean 還是在 XML 檔案中通過 <bean> 進行定義 —— 也就是說,在 XML 配置檔案中定義 Bean,通過
@Autowired
或
@Resource
為 Bean 的成員變量、方法入參或構造函數入參提供自動注入的功能。能否也通過注釋定義 Bean,從 XML 配置檔案中完全移除 Bean 定義的配置呢?答案是肯定的,我們通過 Spring 2.5 提供的
@Component
注釋就可以達到這個目标了。
下面,我們完全使用注釋定義 Bean 并完成 Bean 之間裝配:
清單 20. 使用 @Component 注釋的 Car.java
|
僅需要在類定義處,使用
@Component
注釋就可以将一個類定義了 Spring 容器中的 Bean。下面的代碼将
Office
定義為一個 Bean:
清單 21. 使用 @Component 注釋的 Office.java
|
這樣,我們就可以在 Boss 類中通過
@Autowired
注入前面定義的
Car
和
Office Bean
了。
清單 22. 使用 @Component 注釋的 Boss.java
|
@Component
有一個可選的入參,用于指定 Bean 的名稱,在 Boss 中,我們就将 Bean 名稱定義為“
boss
”。一般情況下,Bean 都是 singleton 的,需要注入 Bean 的地方僅需要通過 byType 政策就可以自動注入了,是以大可不必指定 Bean 的名稱。
在使用
@Component
注釋後,Spring 容器必須啟用類掃描機制以啟用注釋驅動 Bean 定義和注釋驅動 Bean 自動注入的政策。Spring 2.5 對 context 命名空間進行了擴充,提供了這一功能,請看下面的配置:
清單 23. 簡化版的 beans.xml
|
這裡,所有通過 <bean> 元素定義 Bean 的配置内容已經被移除,僅需要添加一行 <context:component-scan/> 配置就解決所有問題了——Spring XML 配置檔案得到了極緻的簡化(當然配置中繼資料還是需要的,隻不過以注釋形式存在罷了)。<context:component-scan/> 的 base-package 屬性指定了需要掃描的類包,類包及其遞歸子包中所有的類都會被處理。
<context:component-scan/> 還允許定義過濾器将基包下的某些類納入或排除。Spring 支援以下 4 種類型的過濾方式,通過下表說明:
表 1. 掃描過濾方式
過濾器類型 | 說明 |
---|---|
注釋 | 假如 com.baobaotao.SomeAnnotation 是一個注釋類,我們可以将使用該注釋的類過濾出來。 |
類名指定 | 通過全限定類名進行過濾,如您可以指定将 com.baobaotao.Boss 納入掃描,而将 com.baobaotao.Car 排除在外。 |
正規表達式 | 通過正規表達式定義過濾的類,如下所示: com\.baobaotao\.Default.* |
AspectJ 表達式 | 通過 AspectJ 表達式定義過濾的類,如下所示: com. baobaotao..*Service+ |
下面是一個簡單的例子:
|
值得注意的是 <context:component-scan/> 配置項不但啟用了對類包進行掃描以實施注釋驅動 Bean 定義的功能,同時還啟用了注釋驅動自動注入的功能(即還隐式地在内部注冊了
AutowiredAnnotationBeanPostProcessor
和
CommonAnnotationBeanPostProcessor
),是以當使用 <context:component-scan/> 後,就可以将 <context:annotation-config/> 移除了。
預設情況下通過
@Component
定義的 Bean 都是 singleton 的,如果需要使用其它作用範圍的 Bean,可以通過
@Scope
注釋來達到目标,如以下代碼所示:
清單 24. 通過 @Scope 指定 Bean 的作用範圍
|
這樣,當從 Spring 容器中擷取
boss
Bean 時,每次傳回的都是新的執行個體了。
回頁首
采用具有特殊語義的注釋
Spring 2.5 中除了提供
@Component
注釋外,還定義了幾個擁有特殊語義的注釋,它們分别是:
@Repository
、
@Service
和
@Controller
。在目前的 Spring 版本中,這 3 個注釋和
@Component
是等效的,但是從注釋類的命名上,很容易看出這 3 個注釋分别和持久層、業務層和控制層(Web 層)相對應。雖然目前這 3 個注釋和
@Component
相比沒有什麼新意,但 Spring 将在以後的版本中為它們添加特殊的功能。是以,如果 Web 應用程式采用了經典的三層分層結構的話,最好在持久層、業務層和控制層分别采用
@Repository
、
@Service
和
@Controller
對分層中的類進行注釋,而用
@Component
對那些比較中立的類進行注釋。
回頁首
注釋配置和 XML 配置的适用場合
是否有了這些 IOC 注釋,我們就可以完全摒除原來 XML 配置的方式呢?答案是否定的。有以下幾點原因:
- 注釋配置不一定在先天上優于 XML 配置。如果 Bean 的依賴關系是固定的,(如 Service 使用了哪幾個 DAO 類),這種配置資訊不會在部署時發生調整,那麼注釋配置優于 XML 配置;反之如果這種依賴關系會在部署時發生調整,XML 配置顯然又優于注釋配置,因為注釋是對 Java 源代碼的調整,您需要重新改寫源代碼并重新編譯才可以實施調整。
- 如果 Bean 不是自己編寫的類(如
、JdbcTemplate
等),注釋配置将無法實施,此時 XML 配置是唯一可用的方式。SessionFactoryBean
- 注釋配置往往是類級别的,而 XML 配置則可以表現得更加靈活。比如相比于
事務注釋,使用 aop/tx 命名空間的事務配置更加靈活和簡單。@Transaction
是以在實作應用中,我們往往需要同時使用注釋配置和 XML 配置,對于類級别且不會發生變動的配置可以優先考慮注釋配置;而對于那些第三方類以及容易發生調整的配置則應優先考慮使用 XML 配置。Spring 會在具體實施 Bean 建立和 Bean 注入之前将這兩種配置方式的元資訊融合在一起。
回頁首
小結
Spring 在 2.1 以後對注釋配置提供了強力的支援,注釋配置功能成為 Spring 2.5 的最大的亮點之一。合理地使用 Spring 2.5 的注釋配置,可以有效減少配置的工作量,提高程式的内聚性。但是這并不意味着傳統 XML 配置将走向消亡,在第三方類 Bean 的配置,以及那些諸如資料源、緩存池、持久層操作模闆類、事務管理等内容的配置上,XML 配置依然擁有不可替代的地位。