Annotation(註解)就是Java提供了一種元程序中的元素關聯任何信息和着任何元數據(metadata)的途徑和方法。Annotion(註解)是一個接口,程序能夠經過反射來獲取指定程序元素的Annotion對象,而後經過Annotion對象來獲取註解裏面的元數據。java
Annotation(註解)是JDK5.0及之後版本引入的。它能夠用於建立文檔,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查。從某些方面看,annotation就像修飾符同樣被使用,並應用於包、類 型、構造方法、方法、成員變量、參數、本地變量的聲明中。這些信息被存儲在Annotation的「name=value」結構對中。程序員
Annotation的成員在Annotation類型中以無參數的方法的形式被聲明。其方法名和返回值定義了該成員的名字和類型。在此有一個特定的默認語法:容許聲明任何Annotation成員的默認值:一個Annotation能夠將name=value對做爲沒有定義默認值的Annotation成員的值,固然也可使用name=value對來覆蓋其它成員默認值。這一點有些近似類的繼承特性,父類的構造函數能夠做爲子類的默認構造函數,可是也能夠被子類覆蓋。api
Annotation能被用來爲某個程序元素(類、方法、成員變量等)關聯任何的信息。須要注意的是,這裏存在着一個基本的規則:Annotation不能影響程序代碼的執行,不管增長、刪除 Annotation,代碼都始終如一的執行。另外,儘管一些annotation經過java的反射api方法在運行時被訪問,而java語言解釋器在工做時忽略了這些annotation。正是因爲java虛擬機忽略了Annotation,致使了annotation類型在代碼中是「不起做用」的; 只有經過某種配套的工具纔會對annotation類型中的信息進行訪問和處理。本文中將涵蓋標準的Annotation和meta-annotation類型,陪伴這些annotation類型的工具是java編譯器(固然要以某種特殊的方式處理它們)。數組
元數據從metadata一詞譯來,就是「關於數據的數據」的意思。元數據能夠用來建立文檔,跟蹤代碼的依賴性,執行編譯時格式檢查,代替已有的配置文件。大體的工做以下:安全
1. 編寫文檔:經過代碼裏標識的元數據生成文檔
2. 代碼分析:經過代碼裏標識的元數據對代碼進行分析
3. 編譯檢查:經過代碼裏標識的元數據讓編譯器能實現基本的編譯檢查app
在Java中元數據以標籤的形式存在於Java代碼中,元數據標籤的存在並不影響程序代碼的編譯和執行,它只是被用來生成其它的文件或針在運行時知道被運行代碼的描述信息。
綜上所述:
第一,元數據以標籤的形式存在於Java代碼中。
第二,元數據描述的信息是類型安全的,即元數據內部的字段都是有明確類型的。
第三,元數據須要編譯器以外的工具額外的處理用來生成其它的程序部件。
第四,元數據能夠只存在於Java源代碼級別,也能夠存在於編譯以後的Class文件內部。ide
Annotation:函數
Annotation使用了在java5.0所帶來的新語法,它的行爲十分相似public、final這樣的修飾符。每一個Annotation具備一個名字和成員個數>=0。每一個Annotation的成員具備被稱爲name=value對的名字和值(就像javabean同樣),name=value裝載了Annotation的信息。工具
Annotation類型:ui
Annotation類型定義了Annotation的名字、類型、成員默認值。一個Annotation類型能夠說是一個特殊的java接口,它的成員變量是受限制的,而聲明Annotation類型時須要使用新語法。當咱們經過java反射api訪問Annotation時,返回值將是一個實現了該 annotation類型接口的對象,經過訪問這個對象咱們能方便的訪問到其Annotation成員。後面的章節將提到在java5.0的 java.lang包裏包含的3個標準Annotation類型。
根據註解參數的個數,咱們能夠將註解分爲三類:
1.標記註解: 一個沒有成員定義的Annotation類型被稱爲標記註解。這種Annotation類型僅使用自身的存在與否來爲咱們提供信息。好比後面的系統註解@Override;
2.單值註解
3.完整註解根據註解使用方法和用途,咱們能夠將Annotation分爲三類:
1.JDK內置系統註解
2.元註解
3.自定義註解
@Override 是一個標記註解類型,它被用做標註方法。它說明了被標註的方法重載了父類的方法,起到了斷言的做用。若是咱們使用了這種Annotation在一個沒有覆蓋父類方法的方法時,java編譯器將以一個編譯錯誤來警示。這個annotaton經常在咱們試圖覆蓋父類方法而確又寫錯了方法名時發揮威力。
使用方法極其簡單:在使用此annotation時只要在被修飾的方法前面加上@Override便可。
Deprecated也是一個標記註解。當一個類型或者類型成員使用@Deprecated修飾的話,編譯器將不鼓勵使用這個被標註的程序元素。並且這種修飾具備必定的 「延續性」:若是咱們在代碼中經過繼承或者覆蓋的方式使用了這個過期的類型或者成員,雖然繼承或者覆蓋後的類型或者成員並非被聲明爲 @Deprecated,但編譯器仍然要報警。
值得注意,@Deprecated這個annotation類型和javadoc中的 @deprecated這個tag是有區別的:前者是java編譯器識別的,然後者是被javadoc工具所識別用來生成文檔(包含程序成員爲何已通過 時、它應當如何被禁止或者替代的描述)。
在java5.0,java編譯器仍然象其從前版本那樣尋找@deprecated這個javadoc tag,並使用它們產生警告信息。可是這種情況將在後續版本中改變,咱們應在如今就開始使用@Deprecated來修飾過期的方法而不是 @deprecated javadoc tag。
@SuppressWarnings 被用於有選擇的關閉編譯器對類、方法、成員變量、變量初始化的警告。在java5.0,sun提供的javac編譯器爲咱們提供了-Xlint選項來使編譯器對合法的程序代碼提出警告,此種警告從某種程度上表明瞭程序錯誤。例如當咱們使用一個generic collection類而又沒有提供它的類型時,編譯器將提示出"unchecked warning"的警告。一般當這種狀況發生時,咱們就須要查找引發警告的代碼。若是它真的表示錯誤,咱們就須要糾正它。例如若是警告信息代表咱們代碼中的switch語句沒有覆蓋全部可能的case,那麼咱們就應增長一個默認的case來避免這種警告。
有時咱們沒法避免這種警告,例如,咱們使用必須和非generic的舊代碼交互的generic collection類時,咱們不能避免這個unchecked warning。此時@SuppressWarning就要派上用場了,在調用的方法前增長@SuppressWarnings修飾,告訴編譯器中止對此方法的警告。
SuppressWarning不是一個標記註解。它有一個類型爲String[]的成員,這個成員的值爲被禁止的警告名。對於javac編譯器來說,被-Xlint選項有效的警告 名也一樣對@SuppressWarings有效,同時編譯器忽略掉沒法識別的警告名。
元註解的做用就是負責註解其餘註解。Java5.0定義了4個標準的meta-annotation類型,它們被用來提供對其它 annotation類型做說明。Java5.0定義的元註解:
1.@Target,
2.@Retention,
3.@Documented,
4.@Inherited
@Target說明了Annotation所修飾的對象範圍:Annotation可被用於 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標。
做用:用於描述註解的使用範圍(即:被描述的註解能夠用在什麼地方)
取值(ElementType)有:
1.CONSTRUCTOR:用於描述構造器
2.FIELD:用於描述域
3.LOCAL_VARIABLE:用於描述局部變量
4.METHOD:用於描述方法
5.PACKAGE:用於描述包
6.PARAMETER:用於描述參數
7.TYPE:用於描述類、接口(包括註解類型) 或enum聲明
@Target(ElementType.TYPE) public @interface Table { /** * 數據表名稱註解,默認值爲類名稱 * @return */ public String tableName() default "className"; } @Target(ElementType.FIELD) public @interface NoDBColumn { }
@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出如今源代碼中,而被編譯器丟棄;而另外一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另外一些在class被裝載時將被讀取(請注意並不影響class的執行,由於Annotation與class在使用上是被分離的)。使用這個meta-Annotation能夠對 Annotation的「生命週期」限制。
做用:表示須要在什麼級別保存該註釋信息,用於描述註解的生命週期(即:被描述的註解在什麼範圍內有效)
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在運行時有效(即運行時保留)Retention meta-annotation類型有惟一的value做爲成員,它的取值來自java.lang.annotation.RetentionPolicy的枚舉類型值
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Column { public String name() default "fieldName"; public String setFuncName() default "setField"; public String getFuncName() default "getField"; public boolean defaultDBValue() default false; } // Column註解的的RetentionPolicy的屬性值是RUTIME,這樣註解處理器能夠經過反射, // 獲取到該註解的屬性值,從而去作一些運行時的邏輯處理
@Documented用於描述其它類型的annotation應該被做爲被標註的程序成員的公共API,所以能夠被例如javadoc此類的工具文檔化。Documented是一個標記註解,沒有成員。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Column { public String name() default "fieldName"; public String setFuncName() default "setField"; public String getFuncName() default "getField"; public boolean defaultDBValue() default false; }
@Inherited 元註解是一個標記註解,@Inherited闡述了某個被標註的類型是被繼承的。若是一個使用了@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類型被發現,或者到達類繼承結構的頂層。
@Inherited public @interface Greeting { public enum FontColor{ BULE,RED,GREEN}; String name(); FontColor fontColor() default FontColor.GREEN; }
定義註解格式:public @interface 註解名 {定義體}
使用@interface自定義註解時,自動繼承了java.lang.annotation.Annotation接口,由編譯程序自動完成其餘細節。
在定義註解時,不能繼承其餘的註解或接口。@interface用來聲明一個註解,其中的每個方法其實是聲明瞭一個配置參數。方法的名稱就是參數的名稱,返回值類型就是參數的類型(返回值類型只能是基本類型、Class、String、enum)。
能夠經過default來聲明參數的默認值。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface MyAnnotation { public String name(); public String value(); }
註解參數的可支持數據類型:
1.全部基本數據類型(int,float,boolean,byte,double,char,long,short)
2.String類型
3.Class類型
4.enum類型
5.Annotation類型
6.以上全部類型的數組Annotation類型裏面的參數該怎麼設定:
第一,只能用public或默認(default)這兩個訪問權修飾.例如,String value();這裏把方法設爲defaul默認類型;
第二,參數成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數據類型和 String,Enum,Class,annotations等數據類型,以及這一些類型的數組.例如,String value();這裏的參數成員就爲String;
第三,若是隻有一個參數成員,最好把參數名稱設爲"value",後加小括號.例:下面的例子FruitName註解就只有一個參數成員。
@FruitColor(fruitColor=Color.RED)
註解元素必須有肯定的值,要麼在定義註解的默認值中指定,要麼在使用註解時指定,非基本類型的註解元素的值不可爲null。所以, 使用空字符串或0做爲默認值是一種經常使用的作法。這個約束使得處理器很難表現一個元素的存在或缺失的狀態,由於每一個註解的聲明中,全部元素都存在,而且都具備相應的值,爲了繞開這個約束,咱們只能定義一些特殊的值,例如空字符串或者負數,一次表示某個元素不存在,在定義註解時,這已經成爲一個習慣用法。例如:
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitProvider { /** * 供應商編號 * @return */ public int id() default -1; /** * 供應商名稱 * @return */ public String name() default ""; /** * 供應商地址 * @return */ public String address() default ""; }
若是沒有用來讀取註解的方法和工做,那麼註解也就不會比註釋更有用處了。使用註解的過程當中,很重要的一部分就是建立於使用註解處理器。Java SE5擴展了反射機制的API,以幫助程序員快速的構造自定義註解處理器。
Java使用Annotation接口來表明程序元素前面的註解,該接口是全部Annotation類型的父接口。除此以外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口表明程序中能夠接受註解的程序元素,該接口主要有以下幾個實現類:
Class:類定義
Constructor:構造器定義
Field:累的成員變量定義
Method:類的方法定義
Package:類的包定義java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包全部提供的反射API擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義爲運行時的Annotation後,該註解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation纔會被虛擬機讀取。
AnnotatedElement 接口是全部程序元素(Class、Method和Constructor)的父接口,因此程序經過反射獲取了某個類的AnnotatedElement對象以後,程序就能夠調用該對象的以下四個個方法來訪問Annotation信息:方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程序元素上存在的、指定類型的註解,若是該類型註解不存在,則返回null。
方法2:Annotation[] getAnnotations():返回該程序元素上存在的全部註解。
方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的註解,存在則返回true,不然返回false.
方法4:Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的全部註釋。與此接口中的其餘方法不一樣,該方法將忽略繼承的註釋。(若是沒有註釋直接存在於此元素上,則返回長度爲零的一個數組。)該方法的調用者能夠隨意修改返回的數組;這不會對其餘調用者返回的數組產生任何影響。
/***********註解聲明***************/ /** * 水果名稱註解 * @author peida * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitName { String value() default ""; } /** * 水果顏色註解 * @author peida * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitColor { /** * 顏色枚舉 * @author peida * */ public enum Color{ BULE,RED,GREEN}; /** * 顏色屬性 * @return */ Color fruitColor() default Color.GREEN; } /** * 水果供應者註解 * @author peida * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitProvider { /** * 供應商編號 * @return */ public int id() default -1; /** * 供應商名稱 * @return */ public String name() default ""; /** * 供應商地址 * @return */ public String address() default ""; } /***********註解使用***************/ public class Apple { @FruitName("Apple") private String appleName; @FruitColor(fruitColor=Color.RED) private String appleColor; @FruitProvider(id=1,name="陝西紅富士集團",address="陝西省西安市延安路89號紅富士大廈") private String appleProvider; public void setAppleColor(String appleColor) { this.appleColor = appleColor; } public String getAppleColor() { return appleColor; } public void setAppleName(String appleName) { this.appleName = appleName; } public String getAppleName() { return appleName; } public void setAppleProvider(String appleProvider) { this.appleProvider = appleProvider; } public String getAppleProvider() { return appleProvider; } public void displayName(){ System.out.println("水果的名字是:蘋果"); } } /***********註解處理器***************/ public class FruitInfoUtil { public static void getFruitInfo(Class<?> clazz){ String strFruitName=" 水果名稱:"; String strFruitColor=" 水果顏色:"; String strFruitProvicer="供應商信息:"; Field[] fields = clazz.getDeclaredFields(); for(Field field :fields){ if(field.isAnnotationPresent(FruitName.class)){ FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class); strFruitName=strFruitName+fruitName.value(); System.out.println(strFruitName); } else if(field.isAnnotationPresent(FruitColor.class)){ FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class); strFruitColor=strFruitColor+fruitColor.fruitColor().toString(); System.out.println(strFruitColor); } else if(field.isAnnotationPresent(FruitProvider.class)){ FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class); strFruitProvicer=" 供應商編號:"+fruitProvider.id()+" 供應商名稱:"+fruitProvider.name()+" 供應商地址:"+fruitProvider.address(); System.out.println(strFruitProvicer); } } } } /***********輸出結果***************/ public class FruitRun { /** * @param args */ public static void main(String[] args) { FruitInfoUtil.getFruitInfo(Apple.class); } }