做者: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的朋友們共享。
閱讀提示:文中提到的程序成員或者程序元素是一個概念,指組成程序代碼的單元:如類、方法、成員變量。
1、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/
2、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:
@Override
public 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(改變前已經被編譯的)也受到影響。
3、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);
}
4、如何自定義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();
}
5、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類型被發現,或者到達類繼承結構的頂層。
6、總結: 本文幾乎覆蓋了全部的Annotation的概念和知識點,從annotation的定義、語法到工做原理、如何自定義annotation,直至meta- annotation。其中也具備一些配套的代碼片段可參考,雖然不是不少,可是可謂言簡意賅、着其重點,本人認爲用好annotation的關鍵還在於使用。但願本手冊可以幫助你們用好annotation,這也是本人的最大快樂。 凡有該標誌的文章,都是該blog博主Caoer(草兒)原創,凡是索引、收藏 、轉載請註明來處和原文做者。很是感謝。