spring MVC 註解詳解以及說明
概述
註釋配置相對於 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.java1 |
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.java01 |
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.java01 |
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.java01 |
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 |
} |
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.java1 |
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 |
} |
當候選 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 > |
當不能確定 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 |
} |
和找不到一個型別匹配 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 允許我們通過 @Qualifier 註釋指定注入 Bean 的名稱,這樣歧義就消除了,可以通過下面的方法解決異常:
清單 13. 使用 @Qualifier 註釋指定注入 Bean 的名稱
1 |
@Autowired |
2 |
public void
setOffice( @Qualifier ( "office" )Office office) { |
3 |
this .office = office; |
4 |
} |
@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 |
} |
使用 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 |
} |
要讓 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 |
} |
我們知道,不管是通過實現 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 |
} |