一、注解 (Annotation) 概述
從 JDK 5.0 開始, Java 增加了對中繼資料(MetaData) 的支援, 也就是Annotation(注解)
Annotation 其實就是代碼裡的特殊标記, 這些标記可以在編譯, 類加載, 運作時被讀取, 并執行相應的處理。通過使用 Annotation, 程式員可以在不改變原有邏輯的情況下, 在源檔案中嵌入一些補充資訊。代碼分析工具、開發工具和部署工具可以通過這些補充資訊進行驗證或者進行部署。
Annotation 可以像修飾符一樣被使用, 可用于修飾包,類, 構造器, 方法, 成員變量, 參數, 局部變量的聲明, 這些資訊被儲存在 Annotation 的 “name=value” 對中。
在JavaSE中,注解的使用目的比較簡單,例如标記過時的功能,忽略警告等。在JavaEE/Android中注解占據了更重要的角色,例如用來配置應用程式的任何切面,代替JavaEE舊版中所遺留的繁冗代碼和XML配置等。
未來的開發模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以後也是基于注解的,現在的Struts2有一部分也是基于注解的了,注解是一種趨勢,一定程度上可以說:架構 = 注解 + 反射 + 設計模式。
注解都是 @ 符号開頭的,例如我們在學習方法重寫時使用過的 @Override 注解。同 Class 和 Interface 一樣,注解也屬于一種類型。
無論是哪一種注解,本質上都一種資料類型,是一種接口類型。到 Java 8 為止 Java SE 提供了 11 個内置注解。其中有 5 個是基本注解,它們來自于 java.lang 包。有 6 個是元注解,它們來自于 java.lang.annotation 包,自定義注解會用到元注解。
提示:元注解就是負責注解其他的注解。
基本注解包括:@Override、@Deprecated、@SuppressWarnings、@SafeVarargs 和 @FunctionalInterface。
二、常見的Annotation示例
使用 Annotation 時要在其前面增加 @ 符号, 并把該 Annotation 當成
一個修飾符使用。用于修飾它支援的程式元素
1、生成文檔相關的注解
- @author 标明開發該類子產品的作者,多個作者之間使用,分割
- @version 标明該類子產品的版本
- @see 參考轉向,也就是相關主題
- @since 從哪個版本開始增加的
- @param 對方法中某參數的說明,如果沒有參數就不能寫
- @return 對方法傳回值的說明,如果方法的傳回值類型是void就不能寫
- @exception 對方法可能抛出的異常進行說明 ,如果方法沒有用throws顯式抛出的異常就不能寫
其中
@param @return 和 @exception 這三個标記都是隻用于方法的。
@param的格式要求:@param 形參名 形參類型 形參說明
@return 的格式要求:@return 傳回值類型 傳回值說明
@exception的格式要求:@exception 異常類型 異常說明
@param和@exception可以并列多個
package com.annotation.javadoc;
/**
* @author shkstart
* @version 1.0
* @see Math.java
*/
public class JavadocTest {
/**
* 程式的主方法,程式的入口
* @param args String[] 指令行參數
*/
public static void main(String[] args) {
}
/**
* 求圓面積的方法
* @param radius double 半徑值
* @return double 圓的面積
*/
public static double getArea(double radius){
return Math.PI * radius * radius; }
}
2、JDK内置的基本注解
- @Override 注解是用來指定方法重寫的,隻能修飾方法并且隻能用于方法重寫,不能修飾其它的元素。它可以強制一個子類必須重寫父類方法或者實作接口的方法。
- @Deprecated 可以用來注解類、接口、成員方法和成員變量等,用于表示某個元素(類、方法等)已過時。通常是因為所修飾的結構危險或存在更好的選擇。當其他程式使用已過時的元素時,編譯器将會給出警告。
- @SuppressWarnings 注解訓示被該注解修飾的程式元素(以及該程式元素中的所有子元素)取消顯示指定的編譯器警告,且會一直作用于該程式元素的所有子元素。例如,使@SuppressWarnings 修飾某個類取消顯示某個編譯器警告,同時又修飾該類裡的某個方法取消顯示另一個編譯器警告,那麼該方法将會同時取消顯示這兩個編譯器警告。
package com.annotation.javadoc;
public class AnnotationTest{
public static void main(String[] args) {
@SuppressWarnings("unused")
int a = 10;
}
@Deprecated
public void print(){
System.out.println("過時的方法");
}
@Override
public String toString() {
return "重寫的toString方法()";
}
}
3、跟蹤代碼依賴性,實作替代配置檔案功能
Servlet3.0提供了注解(annotation),使得不再需要在web.xml檔案中進行Servlet的部署。
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException { }
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
doGet(request, response);
} }
web.xml
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
spring架構中關于“事務”的管理
@Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED,readOnly=false,timeout=3)
public void buyBook(String username, String isbn) {
//1.查詢書的單價
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2. 更新庫存
bookShopDao.updateBookStock(isbn);
//3. 更新使用者的餘額
bookShopDao.updateUserAccount(username, price);
}
<!-- 配置事務屬性 --> <tx:advice transaction-manager="dataSourceTransactionManager" id="txAdvice">
<tx:attributes>
<!-- 配置每個方法使用的事務屬性 --> <tx:method name="buyBook" propagation="REQUIRES_NEW" isolation="READ_COMMITTED" read-only="false" timeout="3" />
</tx:attributes>
</tx:advice>
三、自定義Annotation
- 定義新的 Annotation 類型使用 @interface 關鍵字
- 自定義注解自動繼承了java.lang.annotation.Annotation接口
- Annotation 的成員變量在 Annotation 定義中以無參數方法的形式來聲明。其方法名和傳回值定義了該成員的名字和類型。我們稱為配置參數。類型隻能是八種基本資料類型、String類型、Class類型、enum類型、Annotation類型、以上所有類型的數組。 可以在定義 Annotation 的成員變量時為其指定初始值, 指定成員變量的初始值可使用 default 關鍵字
- 如果隻有一個參數成員,建議使用參數名為value
- 如果定義的注解含有配置參數,那麼使用時必須指定參數值,除非它有預設值。格式是“參數名 = 參數值”,如果隻有一個參數成員,且名稱為value 可以省略“value=”
- 沒有成員定義的 Annotation 稱為标記; 包含成員變量的 Annotation 稱為中繼資料 Annotation
注意:自定義注解必須配上注解的資訊處理流程才有意義。
聲明自定義注解使用 @interface 關鍵字(interface 關鍵字前加 @ 符号)實作。定義注解與定義接口非常像,如下代碼可定義一個簡單形式的注解類型。
public @interface MyAnnotation {
String value() default "hello";
}
public @interface MyAnnotations {
MyAnnotation[] value();
}
四、JDK 中的元注解
JDK 的元 Annotation 用于修飾其他 Annotation 定義
JDK5.0提供了4個标準的meta-annotation類型,分别是:
- Retention
- Target
- Documented
- Inherited
Java 8 又增加了 @Repeatable 和 @Native 兩個注解。這些注解都可以在 java.lang.annotation 包中找到。下面主要介紹每個元注解的作用及使用。
1、@Retention
@Retention: 隻能用于修飾一個 Annotation 定義, 用于指定該 Annotation 的生命周期, @Rentention 包含一個 RetentionPolicy 類型的成員變量, 使用@Rentention 時必須為該 value 成員變量指定值:
- RetentionPolicy.SOURCE:在源檔案中有效(即源檔案保留),編譯器直接丢棄這種政策的注釋
- RetentionPolicy.CLASS:在class檔案中有效(即class保留) , 當運作 Java 程式時, JVM 不會保留注解。 這是預設值
- RetentionPolicy.RUNTIME:在運作時有效(即運作時保留),當運作 Java 程式時, JVM 會保留注釋。程式可以通過反射擷取該注釋。

2、@Target
@Target: 用于修飾 Annotation 定義, 用于指定被修飾的 Annotation 能用于修飾哪些程式元素。 @Target 也包含一個名為 value 的成員變量。
JDK1.8之後,關于元注解@Target的參數類型ElementType枚舉值多了兩個:
TYPE_PARAMETER,TYPE_USE。
在Java 8之前,注解隻能是在聲明的地方所使用,Java8開始,注解可以應用在任何地方。
- ElementType.TYPE_PARAMETER 表示該注解能寫在類型變量的聲明語句中(如:泛型聲明)。
- ElementType.TYPE_USE 表示該注解能寫在使用類型的任何語句中。
3、@Documented
@Documented: 用于指定被該元 Annotation 修飾的 Annotation 類将被javadoc 工具提取成文檔。預設情況下,javadoc是不包括注解的。
定義為Documented的注解必須設定Retention值為RUNTIME。
4、@Inherited
@Inherited: 被它修飾的 Annotation 将具有繼承性。如果某個類使用了被@Inherited 修飾的 Annotation, 則其子類将自動具有該注解。
- 比如:如果把标有@Inherited注解的自定義的注解标注在類級别上,子類則可以繼承父類類級别的注解
- 實際應用中,使用較少
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default "hello";
}
5、@Repeatable
@Repeatable 注解是 Java 8 新增加的,它允許在相同的程式元素中重複注解,在需要對同一種注解多次使用時,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一個程式元素前最多隻能有一個相同類型的注解,如果需要在同一個元素前使用多個相同類型的注解,則必須使用注解“容器”。
Java 8之前
public @interface Roles {
Role[] value();
}
public @interface Role {
String roleName();
}
public class RoleTest {
@Roles(roles = {@Role(roleName = "role1"), @Role(roleName = "role2")})
public String doString(){
return "test";
}
}
Java 8 之後增加了重複注解,使用方式如下:
//先聲明一個Roles用來包含所有的身份
public @interface Roles {
Role[] value();
}
@Repeatable(Roles.class)
public @interface Role {
String roleName();
}
public class RoleTest {
@Role(roleName = "role1")
@Role(roleName = "role2")
public String doString(){
return "test";
}
}
6、@Native
使用 @Native 注解修飾成員變量,則表示這個變量可以被本地代碼引用,常常被代碼生成工具使用。對于 @Native 注解不常使用,了解即可。
六、利用反射擷取注解資訊
JDK 5.0 在 java.lang.reflect 包下新增了 AnnotatedElement 接口, 該接口代表程式中可以接受注解的程式元素
當一個 Annotation 類型被定義為運作時 Annotation 後, 該注解才是運作時可見, 當 class 檔案被載入時儲存在 class 檔案中的 Annotation 才會被虛拟機讀取
程式可以調用 AnnotatedElement對象的如下方法來通路 Annotation 資訊
@Retention(RetentionPolicy.RUNTIME)
@interface PersonInfo {
String name();
int age() default 20;
String gender();
}
class PersonOpe {
@PersonInfo(name="李四",age=20,gender="男")
public void show(String name,int age,String gen) {
System.out.println(name);
System.out.println(age);
System.out.println(gen);
}
}
public class AnnotationTest {
public static void main(String[] args) throws Exception{
PersonOpe ope=new PersonOpe();
Class<?> class1=PersonOpe.class;
Method method = class1.getMethod("show", String.class,int.class,String.class);
PersonInfo annotation = method.getAnnotation(PersonInfo.class);
String name=annotation.name();
int age=annotation.age();
String gender=annotation.gender();
method.invoke(ope, name,age,gender);
}
}
作者:王陸