Java Annotation手冊

來源:互聯網
上載者:User
作者:cleverpig(作者的Blog:http://blog.matrix.org.cn/page/cleverpig)
原文:http://www.matrix.org.cn/resource/article/44/44055_Java+Annotation+Reflect.html
關鍵字:java,annotation,reflect

前言:
在上篇文章《Java Annotation入門》中概要性的介紹了Annotation的定義、使用,範圍涵蓋較廣,但是深度不夠。所以作者在《Java Annotation入門》後,繼續整理了Annotation的概念和知識點,與喜歡research的朋友們共用。

閱讀提示:文中提到的程式成員或者程式元素是一個概念,指組成程式碼的單元:如類、方法、成員變數。

一、Annotation究竟是什嗎?

Annotation 提供了一條與程式元素關聯任何資訊或者任何中繼資料(metadata)的途徑。從某些方面看,annotation就像修飾符一樣被使用,並應用於包、類 型、構造方法、方法、成員變數、參數、本地變數的聲明中。這些資訊被儲存在annotation的“name=value”結構對中。 annotation類型是一種介面,能夠通過java反射API的方式提供對其資訊的訪問。

annotation能被用來為某個程式元 素(類、方法、成員變數等)關聯任何的資訊。需要注意的是,這裡存在著一個基本的潛規則:annotaion不能影響程式碼的執行,無論增加、刪除 annotation,代碼都始終如一的執行。另外,儘管一些annotation通過java的反射api方法在運行時被訪問,而java語言解譯器在 工作時忽略了這些annotation。正是由於java虛擬機器忽略了annotation,導致了annotation類型在代碼中是“不起作用”的; 只有通過某種配套的工具才會對annotation類型中的資訊進行訪問和處理。本文中將涵蓋標準的annotation和meta-
annotation類型,陪伴這些annotation類型的工具是java編譯器(當然要以某種特殊的方式處理它們)。

由於上述原 因,annotation在使用時十分簡便。一個本地變數可以被一個以NonNull命名的annotation類型所標註,來作為對這個本地變數不能被 賦予null值的斷言。而我們可以編寫與之配套的一個annotation程式碼分析工具,使用它來對具有前面變數的代碼進行解析,並且嘗實驗證這個斷言。 當然這些代碼並不必自己編寫。在JDK安裝後,在JDK/bin目錄中可以找到名為“apt”的工具,它提供了處理annotation的架構:它啟動後 掃描原始碼中的annotation,並調用我們定義好的annotation處理器完成我們所要完成的工作(比如驗證前面例子中的斷言)。說到這裡,
annotation的強大功能似乎可以替代XDoclet這類的工具了,隨著我們的深入,大家會更加堅信這一點。
註:詳細描述請參看jsr250規範:
http://www.jcp.org/aboutJava/communityprocess/pfd/jsr250/

二、Annotation的定義:

這 段文字開始介紹annotation相關技術。在此大家將看到java5.0的標準annotation類型,這種標準類型就是前文中所說的“內建”類 型,它們可以直接被javac支援。可喜的是,在java6.0beta版中的javac已經加入了對自訂annotation的支援。

1。Annotation的概念和文法:

首先,關鍵的概念是理解annotation是與一個程式元素相關聯資訊或者中繼資料的標註。它從不影響java程式的執行,但是對例如編譯器警告或者像文檔產生器等協助工具輔助產生影響。

下面是常用的annotation列表,我們應該注意在annotation和annotation類型之間的不同:

A.annotation:
annotation 使用了在java5.0所帶來的新文法,它的行為十分類似public、final這樣的修飾符。每個annotation具有一個名字和成員個數 >=0。每個annotation的成員具有被稱為name=value對的名字和值(就像javabean一樣),name=value裝載了 annotation的資訊。

B.annotation類型:
annotation 類型定義了annotation的名字、類型、成員預設值。一個annotation類型可以說是一個特殊的java介面,它的成員變數是受限制的,而聲 明annotation類型時需要使用新文法。當我們通過java反射api訪問annotation時,傳回值將是一個實現了該annotation類 型介面的對象,通過訪問這個對象我們能方便的訪問到其annotation成員。後面的章節將提到在java5.0的java.lang包裡包含的3個標 准annotation類型。

C.annotation成員:
annotation 的成員在annotation類型中以無參數的方法的形式被聲明。其方法名和傳回值定義了該成員的名字和類型。在此有一個特定的預設文法:允許聲明任何 annotation成員的預設值:一個annotation可以將name=value對作為沒有定義預設值的annotation成員的值,當然也可 以使用name=value對來覆蓋其它成員預設值。這一點有些近似類的繼承特性,父類的建構函式可以作為子類的預設建構函式,但是也可以被子類覆蓋。

D.marker annotation類型:
一個沒有成員定義的annotation類型被稱為marker annotation。這種annotation類型僅使用自身的存在與否來為我們提供資訊。如後面要說的Override。

E.meta-annotation:
meta -annotation也稱為元annotation,它是被用來聲明annotation類型的annotation。Java5.0提供了一些標準的 元-annotation類型。下面介紹的target、retention就是meta-annotation。

F.target:
annotation 的target是一個被標註的程式元素。target說明了annotation所修飾的物件範圍:annotation可被用於packages、 types(類、介面、枚舉、annotation類型)、類型成員(方法、構造方法、成員變數、枚舉值)、方法參數和本地變數(如迴圈變數、catch 參數)。在annotation類型的聲明中使用了target可更加明晰其修飾的目標。

G.retention:
annotation 的retention定義了該annotation被保留的時間長短:某些annotation僅出現在原始碼中,而被編譯器丟棄;而另一些卻被編譯在 class檔案中;編譯在class檔案中的annotation可能會被虛擬機器忽略,而另一些在class被裝載時將被讀取(請注意並不影響class 的執行,因為annotation與class在使用上是被分離的)。使用這個meta-annotation可以對annotation的“生命週期” 限制。

H.metadata:
由於metadata被廣泛使用於各種電腦開發過程中,所以當我們在這裡談論的metadata即中繼資料通常指被annotation裝載的資訊或者annotation本身。

2。使用標準Annotation:
java5.0在java.lang包中定義了3種標準的annotation類型:

A.Override:
java.lang.Override 是一個marker annotation類型,它被用作標註方法。它說明了被標註的方法重載了父類的方法,起到了斷言的作用。如果我們使用了這種annotation在一個 沒有覆蓋父類方法的方法時,java編譯器將以一個編譯錯誤來警示。
這個annotaton常常在我們試圖覆蓋父類方法而確又寫錯了方法名時發揮威力。

使用方法極其簡單:在使用此annotation時只要在被修飾的方法前面加上@Override。
下面的代碼是一個使用@Override修飾一個企圖重載父類的toString方法,而又存在拼字錯誤的sample:
清單1:

@Overridepublic String toSting() {   // 注意方法名拼字錯了    return "[" + super.toString() + "]";}

B.Deprecated:
同 樣Deprecated也是一個marker annotation。當一個類型或者類型成員使用@Deprecated修飾的話,編譯器將不鼓勵使用這個被標註的程式元素。而且這種修飾具有一定的 “延續性”:如果我們在代碼中通過繼承或者覆蓋的方式使用了這個過時的類型或者成員,雖然繼承或者覆蓋後的類型或者成員並不是被聲明為 @Deprecated,但編譯器仍然要警示。
值得注意,@Deprecated這個annotation類型和javadoc中的 @deprecated這個tag是有區別的:前者是java編譯器識別的,而後者是被javadoc工具所識別用來產生文檔(包含程式成員為什麼已經過 時、它應當如何被禁止或者替代的描述)。
在java5.0,java編譯器仍然象其從前版本那樣尋找@deprecated這個javadoc tag,並使用它們產生警告資訊。但是這種狀況將在後續版本中改變,我們應在現在就開始使用@Deprecated來修飾過時的方法而不是 @deprecated javadoc tag。
清單2:

下面是一段使用@Deprecated的代碼:/*** 這裡是javadoc的@deprecated聲明.* @deprecated No one has players for this format any more.  Use VHS instead.*/@Deprecated public class Betamax { ... }

C.SuppressWarnings:
@SuppressWarnings 被用於有選擇的關閉編譯器對類、方法、成員變數、變數初始化的警告。在java5.0,sun提供的javac編譯器為我們提供了-Xlint選項來使編 譯器對合法的程式碼提出警告,此種警告從某種程度上代表了程式錯誤。例如當我們使用一個generic collection類而又沒有提供它的類型時,編譯器將提示出"unchecked warning"的警告。

通常當這種情況發生時,我們就需要尋找引起警告的代碼。如果它真的表示錯誤,我們就需要糾正它。例如如果警告資訊表明我們代碼中的switch語句沒有覆蓋所有可能的case,那麼我們就應增加一個預設的case來避免這種警告。
相 仿,有時我們無法避免這種警告,例如,我們使用必須和非generic的舊代碼互動的generic collection類時,我們不能避免這個unchecked warning。此時@SuppressWarning就要派上用場了,在調用的方法前增加@SuppressWarnings修飾,告訴編譯器停止對此 方法的警告。
SuppressWarning不是一個marker annotation。它有一個類型為String[]的成員,這個成員的值為被禁止的警告名。對於javac編譯器來講,被-Xlint選項有效警告 名也同樣對@SuppressWarings有效,同時編譯器忽略掉無法識別的警告名。

annotation文法允許在annotation名後跟括弧,括弧中是使用逗號分割的name=value對用於為annotation的成員賦值:
清單3:

@SuppressWarnings(value={"unchecked","fallthrough"})public void lintTrap() { /* sloppy method body omitted */ }

在這個例子中SuppressWarnings annotation類型只定義了一個單一的成員,所以只有一個簡單的value={...}作為name=value對。又由於成員值是一個數組,故使用大括弧來聲明數組值。

注意:我們可以在下面的情況中縮寫annotation:當annotation只有單一成員,並成員命名為"value="。這時可以省去"value="。比如將上面的SuppressWarnings annotation進行縮寫:
清單4:

@SuppressWarnings({"unchecked","fallthrough"})


如果SuppressWarnings所聲明的被禁止警告個數為一個時,可以省去大括弧:

@SuppressWarnings("unchecked")

3。Annotation文法:

在上一個章節中,我們看到書寫marker annotation和單一成員annotation的文法。下面本人來介紹一下完整的文法:

annotation 由“@+annotation類型名稱+(..逗號分割的name-value對...)”組成。其中成員可以按照任何的順序。如果annotation 類型定義了某個成員的預設值,則這個成員可以被省略。成員值必須為編譯時間常量、內嵌的annotation或者數組。

下面我們將定義一個 annotation類型名為Reviews,它有一個由@Review annotation數組構成的成員。這個@Review annotation類型有三個成員:"reviewer"是一個字串,"comment" 是一個具有預設值的可選的字串,"grade"是一個Review.Grade枚舉類型值。
清單5:

@Reviews({  // Single-value annotation, so "value=" is omitted here    @Review(grade=Review.Grade.EXCELLENT,            reviewer="df"),    @Review(grade=Review.Grade.UNSATISFACTORY,            reviewer="eg",            comment="This method needs an @Override annotation")})


annotation文法的另一個重要規則是沒有程式成員可以有多於一個的同一annotation執行個體。例如在一個類中簡單的放置多個@Review annotation。這也是在上面代碼中定義@Reviews annotation類型數組的原因。

4。Annotation成員類型和值:

annotation成員必須是非空的編譯時間常量運算式。可用的成員類型為:primitive類型、, String, Class, enumerated類型, annotation類型, 和前面類型的數組。

下面我們定義了一個名為UncheckedExceptions 的annotation類型,它的成員是一個擴充了RuntimeException類的類數組。
清單6:

@UncheckedExceptions({    IllegalArgumentException.class, StringIndexOutOfBoundsException.class})

5。Annotation的目標:

annotation通常被放在類型定義和成員定義的前面。然而它也出現在package、方法參數、本地變數的前面。下面,我們來討論一下這些不大常用的寫法:

package annotation出現在package聲明的前面。
下面的例子package-info.java中不包含任何的公用類型定義,卻包含一個可選的javadoc注釋。
清單7:

/*** This package holds my custom annotation types.*/@com.davidflanagan.annotations.Author("David Flanagan")package com.davidflanagan.annotations;


當package -info.java檔案被編譯時間,它將產生名為包含annotation(特殊的介面)聲明的package-info.class的類。這個介面沒有 成員,它的名字package-info不是一個合法的java標識,所以它不能用在java原始碼中。這個介面的存在只是簡單的被看作一個為 package annotation準備的預留位置。

用於修飾方法參數、catch參數、本地變數的annotation只是簡單的出現 在這些程式成員的修飾符位置。java類檔案格式沒有為本地變數或者catch參數儲存annotation作準備,所以這些annotation總是保 留在原始碼層級(source retention);方法參數annotation能夠儲存在類檔案中,也可以在保留到運行時。

最後,請注意,枚舉類型定義中不允許任何的修飾符修飾其枚舉值。

6。Annotation和預設值:
在Annotation 中,沒有預設值的成員必須有一個成員值。而如何理解預設值是如何被處理就是一個很重要的細節:annotation類型所定義的成員預設值被儲存在 class檔案中,不被編譯到annotation裡面。如果我們修改一個annotation類型使其成員的預設值發生了改變,這個改變對於所有此類型 的annotation中沒有明確提供成員值的成員產生影響(即修改了該成員的成員值)。即使在annotation類型使其成員的預設值被改變後 annotation從沒被重新編譯過,該類型的annotation(改變前已經被編譯的)也受到影響。

三、Annotation工作原理:

Annotation與反射
在java5.0 中Java.lang.reflect提供的反射API被擴充了讀取運行時annotation的能力。讓我們回顧一下前面所講的:一個 annotation類型被定義為runtime retention後,它才是在運行時可見,當class檔案被裝載時被儲存在class檔案中的annotation才會被虛擬機器讀取。那麼 reflect是如何協助我們訪問class中的annotation呢?

下文將在java.lang.reflect用於 annotation的新特性,其中java.lang.reflect.AnnotatedElement是重要的介面,它代表了提供查詢 annotation能力的程式成員。這個介面被java.lang.Package、java.lang.Class實現,並間接地被Method類、 Constructor類、java.lang.reflect的Field類實現。而annotation中的方法參數可以通過Method類、 Constructor類的getParameterAnnotations()方法獲得。

下面的代碼使用了AnnotatedElement類的isAnnotationPresent()方法判斷某個方法是否具有@Unstable annotation,從而斷言此方法是否穩定:
清單8:

import java.lang.reflect.*;Class c = WhizzBangClass.class;                           Method m = c.getMethod("whizzy", int.class, int.class);  boolean unstable = m.isAnnotationPresent(Unstable.class);


isAnnotationPresent ()方法對於檢查marker annotation是十分有用的,因為marker annotation沒有成員變數,所以我們只要知道class的方法是否使用了annotation修飾就可以了。而當處理具有成員的 annotation時,我們通過使用getAnnotation()方法來獲得annotation的成員資訊(成員名稱、成員值)。這裡我們看到了一 套優美的java annotation系統:如果annotation存在,那麼實現了相應的annotation類型介面的對象將被getAnnotation()方法
返回,接著調用定義在annotation類型中的成員方法可以方便地獲得任何成員值。

回想一下,前面介紹的@Reviews annotation,如果這個annotation類型被聲明為runtime retention的話,我們通過下面的代碼來訪問@Reviews annotation的成員值:
清單9:

AnnotatedElement target = WhizzBangClass.class; //獲得被查詢的AnnotatedElement// 查詢AnnotatedElement的@Reviews annotation資訊Reviews annotation = target.getAnnotation(Reviews.class);// 因為@Reviews annotation類型的成員為@Review annotation類型的數組,// 所以下面聲明了Review[] reviews儲存@Reviews annotation類型的value成員值。Review[] reviews = annotation.value();// 查詢每個@Review annotation的成員資訊for(Review r : reviews) {    Review.Grade grade = r.grade();    String reviewer = r.reviewer();    String comment = r.comment();    System.out.printf("%s assigned a grade of %s and comment '%s'%n",                      reviewer, grade, comment);}

四、如何自訂Annotation?

1.詳解annotation與介面的異同:
因為annotation類型是一個非凡的介面,所以兩者之間存在著某些差異:

A.Annotation類型使用關鍵字@interface而不是interface。
這個關鍵字聲明隱含了一個資訊:它是繼承了java.lang.annotation.Annotation介面,並非聲明了一個interface。

B.Annotation類型、方法定義是獨特的、受限制的。
Annotation 類型的方法必須聲明為無參數、無異常拋出的。這些方法定義了annotation的成員:方法名成為了成員名,而方法傳回值成為了成員的類型。而方法返回 實值型別必須為primitive類型、Class類型、枚舉類型、annotation類型或者由前面類型之一作為元素的一維數組。方法的後面可以使用 default和一個預設數值來聲明成員的預設值,null不能作為成員預設值,這與我們在非annotation類型中定義方法有很大不同。
Annotation類型和它的方法不能使用annotation類型的參數、成員不能是generic。只有傳回值類型是Class的方法可以在annotation類型中使用generic,因為此方法能夠用類轉換將各種類型轉換為Class。

C.Annotation類型又與介面有著近似之處。
它們可以定義常量、靜態成員類型(比如枚舉類型定義)。Annotation類型也可以如介面一般被實現或者繼承。

2.執行個體:
下面,我們將看到如何定義annotation類型的example。它展示了annotation型別宣告以及@interface與interface之間的不同:
清單10:

package com.davidflanagan.annotations;import java.lang.annotation.*;/*** 使用annotation來描述那些被標註的成員是不穩定的,需要更改*/@Retention(RetentionPolicy.RUNTIME)public @interface Unstable {}

下面的另一個example只定義了一個成員。並通過將這個成員命名為value,使我們可以方便的使用這種annotation的快捷聲明方式:
清單11:

/*** 使用Author這個annotation定義在程式中指出代碼的作者*/public @interface Author {    /** 返回作者名 */    String value();}

以 下的example更加複雜。Reviews annotation類型只有一個成員,但是這個成員的類型是複雜的:由Review annotation組成的數組。Review annotation類型有3個成員:枚舉類型成員grade、表示Review名稱的字串類型成員Reviewer、具有預設值的字串類型成員 Comment。
清單12:

import java.lang.annotation.*;        /*** Reviews annotation類型只有一個成員,* 但是這個成員的類型是複雜的:由Review annotation組成的數組*/@Retention(RetentionPolicy.RUNTIME)public @interface Reviews {    Review[] value();}/*** Review annotation類型有3個成員: * 枚舉類型成員grade、  * 表示Review名稱的字串類型成員Reviewer、  * 具有預設值的字串類型成員Comment。*/public @interface Review {    // 內嵌的枚舉類型    public static enum Grade { EXCELLENT, SATISFACTORY, UNSATISFACTORY };    // 下面的方法定義了annotation的成員    Grade grade();                    String reviewer();              String comment() default "";  }

最 後,我們來定義一個annotation方法用於羅列出類運行中所有的unchecked異常(上文已經提到這種情況不一定是錯誤)。這個 annotation類型將一個數組作為了唯一的成員。數組中的每個元素都是異常類。為了加強對未檢查的異常(此類異常都是在運行時拋出)進行報告,我們 可以在代碼中對異常的類型進行限制:
清單13:

public @interface UncheckedExceptions {    Class<? extends RuntimeException>[] value();}

五、Meta-Annotation

Annotation 類型可以被它們自己所標註。Java5.0定義了4個標準的meta-annotation類型,它們被用來提供對其它annotation類型作說明。 這些類型和它們所支援的類在java.lang.annotation包中可以找到。如果需要更詳細的資訊可以參考jdk5.0手冊。

1.再談Target
作 為meta-annotation類型的Target,它描述了annotation所修飾的程式成員的類型。當一個annotation類型沒有 Target時,它將被作為普通的annotation看待。當將它修飾一個特定的程式成員時,它將發揮其應用的作用,例如:Override用於修飾方 法時,增加了@Target這個meta-annotation就使編譯器對annotation作檢查,從而去掉修飾錯誤類型的Override。

Target meta-annotation類型有唯一的value作為成員。這個成員的類型是java.lang.annotation.ElementType[]類型的,ElementType類型是可以被標註的程式成員的枚舉類型。

2.Retention的用法
我 們在文章的開頭曾經提到過Retention,但是沒有詳細講解。Retention描述了annotation是否被編譯器丟棄或者保留在class文 件;如果保留在class檔案中,是否在class檔案被裝載時被虛擬機器讀取。預設情況下,annotation被儲存在class檔案中,但在運行時並 不能被反射訪問。Retention具有三個取值:source、class、runtime,這些取值來自 java.lang.annotation.RetentionPolicy的枚舉類型值。

Retention meta-annotation類型有唯一的value作為成員,它的取值來自java.lang.annotation.RetentionPolicy的枚舉類型值。

3.Documented
Documented是一個meta-annotation類型,用於描述其它類型的annotation應該被作為被標註的程式成員的公用API,因此可以被例如javadoc此類的工具文檔化。

Documented是一個marker annotation,沒有成員。

4.Inherited
@Inherited meta-annotation也是一個marker annotation,它闡述了某個被標註的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用於一個class, 則這個annotation將被用於該class的子類。

注意:@Inherited annotation類型是被標註過的class的子類所繼承。類並不從它所實現的介面繼承annotation,方法並不從它所重載的方法繼承annotation。

值 得思考的是,當@Inherited annotation類型標註的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼 承性。如果我們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工作:檢查class和其父類,直到發現指定的annotation類型被發現, 或者到達類繼承結構的頂層。

六、總結:

本文幾乎 覆蓋了所有的Annotation的概念和知識點,從annotation的定義、文法到工作原理、如何自訂annotation,直至meta- annotation。其中也具有一些配套的代碼片斷可參考,雖然不是很多,但是可謂言簡意賅、著其重點,本人認為用好annotation的關鍵還在於 使用。希望本手冊能夠協助大家用好annotation,這也是本人的最大快樂。

凡是有該標誌的文章,都是該blog博主Caoer(草兒)原創,凡是索引、收藏
、轉載請註明來處和原文作者。非常感謝。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.