1. 程式人生 > >spring MVC 註解詳解以及說明

spring MVC 註解詳解以及說明

基於註釋(Annotation)的配置有越來越流行的趨勢,Spring 2.5 順應這種趨勢,提供了完全基於註釋配置 Bean、裝配 Bean 的功能,您可以使用基於註釋的 Spring IoC 替換原來基於 XML 的配置。本文通過例項詳細講述了 Spring 2.5 基於註釋 IoC 功能的使用。

概述

註釋配置相對於 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
1 package com.baobaotao;
2 public class Office {
3 private String officeNo =”001”;
4 //省略 get/setter
5 @Override
6 public String toString() {
7 return "officeNo:" + officeNo;
8 }
9 }

Car 擁有兩個屬性:

清單 2. Car.java
01 package com.baobaotao;
02 public class Car {
03 private String brand;
04 private double price;
05 // 省略 get/setter
06 @Override
07 public String toString() {
08 return "brand:" + brand + "," + "price:" + price;
09 }
10 }

Boss 擁有 Office 和 Car 型別的兩個屬性:

清單 3. Boss.java
01 package com.baobaotao;
02 public class Boss {
03 private Car car;
04 private Office office;
05 // 省略 get/setter
06 @Override
07 public String toString() {
08 return "car:" + car + "\n" + "office:" + office;
09 }
10 }

我們在 Spring 容器中將 Office 和 Car 宣告為 Bean,並注入到 Boss Bean 中:下面是使用傳統 XML 完成這個工作的配置檔案 beans.xml:

清單 4. beans.xml 將以上三個類配置成 Bean:

01 <?xml version="1.0" encoding="UTF-8" ?>
02 <beans xmlns="http://www.springframework.org/schema/beans"
03 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04 xsi:schemaLocation="http://www.springframework.org/schema/beans
05 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
06 <bean id="boss" class="com.baobaotao.Boss">
07 <property name="car" ref="car"/>
08 <property name="office" ref="office" />
09 </bean>
10 <bean id="office" class="com.baobaotao.Office">
11 <property name="officeNo" value="002"/>
12 </bean>
13 <bean id="car" class="com.baobaotao.Car" scope="singleton">
14 <property name="brand" value=" 紅旗 CA72"/>
15 <property name="price" value="2000"/>
16 </bean>
17 </beans>

當我們執行以下程式碼時,控制檯將正確打出 boss 的資訊:

清單 5. 測試類:AnnoIoCTest.java 
01 import org.springframework.context.ApplicationContext;
02 import org.springframework.context.support.ClassPathXmlApplicationContext;
03 public class AnnoIoCTest {
04 public static void main(String[] args) {
05 String[] locations = {"beans.xml"};
06 ApplicationContext ctx =
07 new ClassPathXmlApplicationContext(locations);
08 Boss boss = (Boss) ctx.getBean("boss");
09 System.out.println(boss);
10 }
11 }
這說明 Spring 容器已經正確完成了 Bean 建立和裝配的工作。

Spring2.5的註釋

Spring 2.5 提供了 AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、 PersistenceAnnotationBeanPostProcessor 和 RequiredAnnotationBeanPostProcessor 這四個主要的關於 Annotation 的 BeanPostProcessor。
我們可以使用 <context:annotation-config /> 來方便地、一次性的宣告者四個 BeanPostProcessor。
1.Autowired... 提供對 Spring 特有的 Autowired 和 Qualifier 註釋。
2.CommonAnotation... 用於支援 JSR 250 的註釋
3.Persistence... 用於 JPA 的 PersistenceUnit 和PersistenceContext 註釋
4.Required... 用於檢查被 Required 註釋標記的屬性是否被設定

使用 @Autowired 註釋(按型別匹配)

Spring 2.5 引入了 @Autowired 註釋,它可以對類成員變數、方法及建構函式進行標註,完成自動裝配的工作。來看一下使用 @Autowired 進行成員變數自動注入的程式碼:

清單 6. 使用 @Autowired 註釋的 Boss.java
1 package com.baobaotao;
2 import org.springframework.beans.factory.annotation.Autowired;
3 public class Boss {
4 @Autowired
5 private Car car;
6 @Autowired
7 private Office office;
8
9 }

Spring 通過一個 BeanPostProcessor 對 @Autowired 進行解析,所以要讓 @Autowired 起作用必須事先在 Spring 容器中宣告AutowiredAnnotationBeanPostProcessor Bean。

清單 7. 讓 @Autowired 註釋工作起來
01 <?xml version="1.0" encoding="UTF-8" ?>
02 <beans xmlns="http://www.springframework.org/schema/beans"
03 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04 xsi:schemaLocation="http://www.springframework.org/schema/beans
05 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
06 <!-- 該 BeanPostProcessor 將自動起作用,對標註 @Autowired 的 Bean 進行自動注入 -->
07 <bean class="org.springframework.beans.factory.annotation.
08 AutowiredAnnotationBeanPostProcessor"/>
09 <!-- 移除 boss Bean 的屬性注入配置的資訊 -->
10 <bean id="boss" class="com.baobaotao.Boss"/>
11 <bean id="office" class="com.baobaotao.Office">
12 <property name="officeNo" value="001"/>
13 </bean>
14 <bean id="car" class="com.baobaotao.Car" scope="singleton">
15 <property name="brand" value=" 紅旗 CA72"/>
16 <property name="price" value="2000"/>
17 </bean>
18 </beans>

這樣,當 Spring 容器啟動時,AutowiredAnnotationBeanPostProcessor 將掃描 Spring 容器中所有 Bean,當發現 Bean 中擁有 @Autowired 註釋時就找到和其匹配(預設按型別匹配)的 Bean,並注入到對應的地方中去。
按照上面的配置,Spring 將直接採用 Java 反射機制對 Boss 中的 car 和 office 這兩個私有成員變數進行自動注入。所以對成員變數使用 @Autowired 後,您大可將它們的 setter 方法(setCar() 和 setOffice())從 Boss 中刪除。
當然,您也可以通過 @Autowired 對方法或建構函式進行標註,來看下面的程式碼:

清單 8. 將 @Autowired 註釋標註在 Setter 方法上

01 package com.baobaotao;
02 public class Boss {
03 private Car car;
04 private Office office;
05 @Autowired
06 public void setCar(Car car) {
07 this.car = car;
08 }
09
10 @Autowired
11 public void setOffice(Office office) {
12 this.office = office;
13 }
14
15 }

這時,@Autowired 將查詢被標註的方法的入參型別的 Bean,並呼叫方法自動注入這些 Bean。而下面的使用方法則對建構函式進行標註:

清單 9. 將 @Autowired 註釋標註在建構函式上

01 package com.baobaotao;
02 public class Boss {
03 private Car car;
04 private Office office;
05
06 @Autowired
07 public Boss(Car car ,Office office){
08 this.car = car;
09 this.office = office ;
10 }
11
12
13 }
由於 Boss() 建構函式有兩個入參,分別是 car 和 office,@Autowired 將分別尋找和它們型別匹配的 Bean,將它們作為 Boss(Car car ,Office office) 的入參來建立 Boss Bean。
當候選 Bean 數目不為 1 時的應對方法

在預設情況下使用 @Autowired 註釋進行自動注入時,Spring 容器中匹配的候選 Bean 數目必須有且僅有一個。當找不到一個匹配的 Bean 時,Spring 容器將丟擲 BeanCreationException 異常,並指出必須至少擁有一個匹配的 Bean。我們可以來做一個實驗:

清單 10. 候選 Bean 數目為 0 時

01 <?xml version="1.0" encoding="UTF-8" ?>
02 <beans xmlns="http://www.springframework.org/schema/beans"
03 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
04 xsi:schemaLocation="http://www.springframework.org/schema/beans
05 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd ">
06
07 <bean class="org.springframework.beans.factory.annotation.
08 AutowiredAnnotationBeanPostProcessor"/>
09 <bean id="boss" class="com.baobaotao.Boss"/>
10 <!-- 將 office Bean 註釋掉 -->
11 <!-- <bean id="office" class="com.baobaotao.Office">
12 <property name="officeNo" value="001"/>
13 </bean>-->
14 <bean id="car" class="com.baobaotao.Car" scope="singleton">
15 <property name="brand" value=" 紅旗 CA72"/>
16 <property name="price" value="2000"/>
17 </bean>
18 </beans>
由於 office Bean 被註釋掉了,所以 Spring 容器中將沒有型別為 Office 的 Bean 了,而 Boss 的 office 屬性標註了 @Autowired,當啟動 Spring 容器時,異常就產生了。

當不能確定 Spring 容器中一定擁有某個類的 Bean 時,可以在需要自動注入該類 Bean 的地方可以使用 @Autowired(required = false),這等於告訴 Spring:在找不到匹配 Bean 時也不報錯。來看一下具體的例子:

清單 11. 使用 @Autowired(required = false)

01 package com.baobaotao;
02 import org.springframework.beans.factory.annotation.Autowired;
03 import org.springframework.beans.factory.annotation.Required;
04 public class Boss {
05 private Car car;
06 private Office office;
07 @Autowired
08 public void setCar(Car car) {
09 this.car = car;
10 }
11 @Autowired(required = false)
12 public void setOffice(Office office) {
13 this.office = office;
14 }
15
16 }
當然,一般情況下,使用 @Autowired 的地方都是需要注入 Bean 的,使用了自動注入而又允許不注入的情況一般僅會在開發期或測試期碰到(如為了快速啟動 Spring 容器,僅引入一些模組的 Spring 配置檔案),所以 @Autowired(required = false) 會很少用到。

和找不到一個型別匹配 Bean 相反的一個錯誤是:如果 Spring 容器中擁有多個候選 Bean,Spring 容器在啟動時也會丟擲 BeanCreationException 異常。來看下面的例子:

清單 12. 在 beans.xml 中配置兩個 Office 型別的 Bean

1
2 <bean id="office" class="com.baobaotao.Office">
3 <property name="officeNo" value="001"/>
4 </bean>
5 <bean id="office2" class="com.baobaotao.Office">
6 <property name="officeNo" value="001"/>
7 </bean>
8
我們在 Spring 容器中配置了兩個型別為 Office 型別的 Bean,當對 Boss 的 office 成員變數進行自動注入時,Spring 容器將無法確定到底要用哪一個 Bean,因此異常發生了。

Spring 允許我們通過 @Qualifier 註釋指定注入 Bean 的名稱,這樣歧義就消除了,可以通過下面的方法解決異常:

清單 13. 使用 @Qualifier 註釋指定注入 Bean 的名稱

1 @Autowired
2 public void setOffice(@Qualifier("office")Office office) {
3 this.office = office;
4 }
@Qualifier("office") 中的 office 是 Bean 的名稱,所以 @Autowired 和 @Qualifier 結合使用時,自動注入的策略就從 byType 轉變成 byName 了。
@Autowired 可以對成員變數、方法以及建構函式進行註釋,而 @Qualifier 的標註物件是成員變數、方法入參、建構函式入參。正是由於註釋物件的不同,所以 Spring 不將 @Autowired 和 @Qualifier 統一成一個註釋類。下面是對成員變數和建構函式入參進行註釋的程式碼:

對成員變數進行註釋:

清單 14. 對成員變數使用 @Qualifier 註釋

1 public class Boss {
2 @Autowired
3 private Car car;
4
5 @Autowired
6 @Qualifier("office")
7 private Office office;
8
9 }

對建構函式入參進行註釋:

清單 15. 對建構函式變數使用 @Qualifier 註釋

1 public class Boss {
2 private Car car;
3 private Office office;
4 @Autowired
5 public Boss(Car car , @Qualifier("office")Office office){
6 this.car = car;
7 this.office = office ;
8 }
9 }
@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

01 package com.baobaotao;
02 import javax.annotation.Resource;
03 public class Boss {
04 // 自動注入型別為 Car 的 Bean
05 @Resource
06 private Car car;
07 // 自動注入 bean 名稱為 office 的 Bean
08 @Resource(name = "office")
09 private Office office;
10 }
一般情況下,我們無需使用類似於 @Resource(type=Car.class) 的註釋方式,因為 Bean 的型別資訊可以通過 Java 反射從程式碼中獲取。
要讓 JSR-250 的註釋生效,除了在 Bean 類中標註這些註釋外,還需要在 Spring 容器中註冊一個負責處理這些註釋的 BeanPostProcessor:
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
CommonAnnotationBeanPostProcessor 實現了 BeanPostProcessor 介面,它負責掃描使用了 JSR-250 註釋的 Bean,並對它們進行相應的操作。
@PostConstruct 和 @PreDestroy
Spring 容器中的 Bean 是有生命週期的,Spring 允許在 Bean 在初始化完成後以及 Bean 銷燬前執行特定的操作,您既可以通過實現 InitializingBean/DisposableBean 介面來定製初始化之後 / 銷燬之前的操作方法,也可以通過 <bean> 元素的 init-method/destroy-method 屬性指定初始化之後 / 銷燬之前呼叫的操作方法。

JSR-250 為初始化之後/銷燬之前方法的指定定義了兩個註釋類,分別是 @PostConstruct 和 @PreDestroy,這兩個註釋只能應用於方法上。標註了 @PostConstruct 註釋的方法將在類例項化後呼叫,而標註了 @PreDestroy 的方法將在類銷燬之前呼叫。

清單 17. 使用 @PostConstruct 和 @PreDestroy 註釋的 Boss.java

01 package com.baobaotao;
02 import javax.annotation.Resource;
03 import javax.annotation.PostConstruct;
04 import javax.annotation.PreDestroy;
05 public class Boss {
06 @Resource
07 private Car car;
08 @Resource(name = "office")
09 private Office office;
10 @PostConstruct
11 public void postConstruct1(){
12 System.out.println("postConstruct1");
13 }
14 @PreDestroy
15 public void preDestroy1(){
16 System.out.println("preDestroy1");
17 }
18
19 }
您只需要在方法前標註 @PostConstruct 或 @PreDestroy,這些方法就會在 Bean 初始化後或銷燬之前被 Spring 容器執行了。
我們知道,不管是通過實現 InitializingBean/DisposableBean 介面,還是通過 <bean> 元素的 init-method/destroy-method 屬性進行配置,都只能為 Bean 指定一個初始化 / 銷燬的方法。但是使用 @PostConstruct 和 @PreDestroy 註釋卻可以指定多個初始化 / 銷燬方法,那些被標註 @PostConstruct 或@PreDestroy 註釋的方法都會在初始化 / 銷燬時被執行。

通過以下的測試程式碼,您將可以看到 Bean 的初始化 / 銷燬方法是如何被執行的:

清單 18. 測試類程式碼

01 package com.baobaotao;
02 import org.springframework.context.support.ClassPathXmlApplicationContext;
03 public class AnnoIoCTest {
04 public static void main(String[] args) {
05 String[] locations = {"beans.xml"};
06 ClassPathXmlApplicationContext ctx =
07 new ClassPathXmlApplicationContext(locations);
08 Boss boss = (Boss) ctx.getBean("boss");
09 System.out.println(boss);
10 ctx.destroy();// 關閉 Spring 容器,以觸發 Bean 銷燬方法的執行
11 }
<