什么是注解(Annotation):
Annotation(注解)就是Java提供了一種元程序中的元素關聯任何信息和著任何元數據(metadata)的途徑和方法。Annotion(注解)是一個接口,程序可以通過反射來獲取指定程序元素的Annotion對象,然后通過Annotion對象來獲取注解里面的元數據。
Annotation(注解)是JDK5.0及以后版本引入的。它可以用于創建文檔,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查。從某些方面看,annotation就像修飾符一樣被使用,并應用于包、類 型、構造方法、方法、成員變量、參數、本地變量的聲明中。這些信息被存儲在Annotation的“name=value”結構對中。
Annotation的成員在Annotation類型中以無參數的方法的形式被聲明。其方法名和返回值定義了該成員的名字和類型。在此有一個特定的默認語法:允許聲明任何Annotation成員的默認值:一個Annotation可以將name=value對作為沒有定義默認值的Annotation成員的值,當然也可以使用name=value對來覆蓋其它成員默認值。這一點有些近似類的繼承特性,父類的構造函數可以作為子類的默認構造函數,但是也可以被子類覆蓋。
Annotation能被用來為某個程序元素(類、方法、成員變量等)關聯任何的信息。需要注意的是,這里存在著一個基本的規則:Annotation不能影響程序代碼的執行,無論增加、刪除 Annotation,代碼都始終如一的執行。另外,盡管一些annotation通過java的反射api方法在運行時被訪問,而java語言解釋器在工作時忽略了這些annotation。正是由于java虛擬機忽略了Annotation,導致了annotation類型在代碼中是“不起作用”的; 只有通過某種配套的工具才會對annotation類型中的信息進行訪問和處理。本文中將涵蓋標準的Annotation和meta-annotation類型,陪伴這些annotation類型的工具是java編譯器(當然要以某種特殊的方式處理它們)。
什么是metadata(元數據):
元數據從metadata一詞譯來,就是“關于數據的數據”的意思。 元數據的功能作用有很多,比如:你可能用過Javadoc的注釋自動生成文檔。這就是元數據功能的一種。總的來說,元數據可以用來創建文檔,跟蹤代碼的依賴性,執行編譯時格式檢查,代替已有的配置文件。如果要對于元數據的作用進行分類,目前還沒有明確的定義,不過我們可以根據它所起的作用,大致可分為三類: 1. 編寫文檔:通過代碼里標識的元數據生成文檔 2. 代碼分析:通過代碼里標識的元數據對代碼進行分析 3. 編譯檢查:通過代碼里標識的元數據讓編譯器能實現基本的編譯檢查 在Java中元數據以標簽的形式存在于Java代碼中,元數據標簽的存在并不影響程序代碼的編譯和執行,它只是被用來生成其它的文件或針在運行時知道被運行代碼的描述信息。 綜上所述: 第一,元數據以標簽的形式存在于Java代碼中。 第二,元數據描述的信息是**類型安全
**的,即元數據內部的字段都是有明確類型的。 第三,元數據需要編譯器之外的工具額外的處理用來生成其它的程序部件。 第四,元數據可以只存在于Java源代碼級別,也可以存在于編譯之后的Class文件內部。
Annotation和Annotation類型:
Annotation:
Annotation使用了在java5.0所帶來的新語法,它的行為十分類似public、final這樣的修飾符。每個Annotation具有一個名字和成員個數>=0。每個Annotation的成員具有被稱為name=value對的名字和值(就像javabean一樣),name=value裝載了Annotation的信息。
Annotation類型:
Annotation類型定義了Annotation的名字、類型、成員默認值。一個Annotation類型可以說是一個特殊的java接口,它的成員變量是受限制的,而聲明Annotation類型時需要使用新語法。當我們通過java反射api訪問Annotation時,返回值將是一個實現了該 annotation類型接口的對象,通過訪問這個對象我們能方便的訪問到其Annotation成員。后面的章節將提到在java5.0的 java.lang包里包含的3個標準Annotation類型。
注解的分類:
根據注解參數的個數,我們可以將注解分為三類:
標記注解:一個沒有成員定義的Annotation類型被稱為標記注解。這種Annotation類型僅使用自身的存在與否來為我們提供信息。比如后面的系統注解@Override;
單值注解
完整注解
根據注解使用方法和用途,我們可以將Annotation分為三類:
JDK內置系統注解
元注解
自定義注解
系統內置標準注解:
注解的語法比較簡單,除了@符號的使用外,他基本與Java固有的語法一致,JavaSE中內置三個標準注解,定義在java.lang中:
@Override:用于修飾此方法覆蓋了父類的方法;
@Deprecated:用于修飾已經過時的方法;
@SuppressWarnnings:用于通知java編譯器禁止特定的編譯警告。
下面我們依次看看三個內置標準注解的作用和使用場景。
@Override,限定重寫父類方法:
@Override 是一個標記注解類型,它被用作標注方法。它說明了被標注的方法重載了父類的方法,起到了斷言的作用。如果我們使用了這種Annotation在一個沒有覆蓋父類方法的方法時,java編譯器將以一個編譯錯誤來警示。這個annotaton常常在我們試圖覆蓋父類方法而又寫錯了方法名時發揮威力。使用方法極其簡單:在使用此annotation時只要在被修飾的方法前面加上@Override即可。下面的代碼是一個使用@Override修飾一個企圖重載父類的displayName()方法,而又存在拼寫錯誤的實例:
public class Fruit {? public void displayName(){ System.out.println("水果的名字是:*****"); }}?class Orange extends Fruit { @Override public void displayName(){ System.out.println("水果的名字是:桔子"); }}?class Apple extends Fruit { @Override public void displayname(){ System.out.println("水果的名字是:蘋果"); }}
Orange 類編譯不會有任何問題,Apple 類在編譯的時候會提示相應的錯誤。@Override注解只能用于方法,不能用于其他程序元素。
@Deprecated,標記已過時:
同樣Deprecated也是一個標記注解。當一個類型或者類型成員使用@Deprecated修飾的話,編譯器將不鼓勵使用這個被標注的程序元素。而且這種修飾具有一定的 “延續性”:如果我們在代碼中通過繼承或者覆蓋的方式使用了這個過時的類型或者成員,雖然繼承或者覆蓋后的類型或者成員并不是被聲明為 @Deprecated,但編譯器仍然要報警。
值得注意,@Deprecated這個annotation類型和javadoc中的 @deprecated這個tag是有區別的:前者是java編譯器識別的,而后者是被javadoc工具所識別用來生成文檔(包含程序成員為什么已經過時、它應當如何被禁止或者替代的描述)。
在java5.0,java編譯器仍然象其從前版本那樣尋找@deprecated這個javadoc tag,并使用它們產生警告信息。但是這種狀況將在后續版本中改變,我們應在現在就開始使用@Deprecated來修飾過時的方法而不是 @deprecated javadoc tag。
下面一段程序中使用了@Deprecated注解標示方法過期,同時在方法注釋中用@deprecated tag 標示該方法已經過時,代碼如下:
class AppleService { public void displayName(){ System.out.println("水果的名字是:蘋果"); } /** * @deprecated 該方法已經過期,不推薦使用 / @Deprecated public void showTaste(){ System.out.println("水果的蘋果的口感是:脆甜"); } public void showTaste(int typeId){ if(typeId==1){ System.out.println("水果的蘋果的口感是:酸澀"); } else if(typeId==2){ System.out.println("水果的蘋果的口感是:綿甜"); } else{ System.out.println("水果的蘋果的口感是:脆甜"); } }}?public class FruitRun {? /* * @param args */ public static void main(String[] args) { Apple apple=new Apple(); apple.displayName(); AppleService appleService=new AppleService(); appleService.showTaste(); appleService.showTaste(0); appleService.showTaste(2); }?}
AppleService類的showTaste() 方法被@Deprecated標注為過時方法,在FruitRun類中使用的時候,編譯器會給出該方法已過期,不推薦使用的提示。
SuppressWarnnings,抑制編譯器警告:
@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有效,同時編譯器忽略掉無法識別的警告名。
annotation語法允許在annotation名后跟括號,括號中是使用逗號分割的name=value對用于為annotation的成員賦值。實例如下:
public class FruitService { @SuppressWarnings(value={ "rawtypes", "unchecked" }) public static List<Fruit> getFruitList(){ List<Fruit> fruitList=new ArrayList(); return fruitList; } @SuppressWarnings({ "rawtypes", "unchecked" }) public static List<Fruit> getFruit(){ List<Fruit> fruitList=new ArrayList(); return fruitList; }? @SuppressWarnings("unused") public static void main(String[] args){ List<String> strList=new ArrayList<String>(); }}
在這個例子中SuppressWarnings annotation類型只定義了一個單一的成員,所以只有一個簡單的value={...}作為name=value對。又由于成員值是一個數組,故使用大括號來聲明數組值。注意:我們可以在下面的情況中縮寫annotation:當annotation只有單一成員,并成員命名為"value="。這時可以省去"value="。比如將上面方法getFruit()的SuppressWarnings annotation就是縮寫的。
SuppressWarnings注解的常見參數值的簡單說明:
deprecation:使用了不贊成使用的類或方法時的警告;
unchecked:執行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics) 來指定集合保存的類型;
fallthrough:當 Switch 程序塊直接通往下一種情況而沒有 Break 時的警告;
path:在類路徑、源文件路徑等中有不存在的路徑時的警告;
serial:當在可序列化的類上缺少 serialVersionUID 定義時的警告;
finally:任何 finally 子句不能正常完成時的警告;
all:關于以上所有情況的警告。
從JDK5開始,Java增加了Annotation(注解),Annotation是代碼里的特殊標記,這些標記可以在編譯、類加載、運行時被讀取,并執行相應的處理。通過使用Annotation,開發人員可以在不改變原有邏輯的情況下,在源文件中嵌入一些補充的信息。代碼分析工具、開發工具和部署工具可以通過這些補充信息進行驗證、處理或者進行部署。
Annotation提供了一種為程序元素(包、類、構造器、方法、成員變量、參數、局域變量)設置元數據的方法。Annotation不能運行,它只有成員變量,沒有方法。Annotation跟public、final等修飾符的地位一樣,都是程序元素的一部分,Annotation不能作為一個程序元素使用。
1 定義Annotation
定義新的Annotation類型使用@interface關鍵字(在原有interface關鍵字前增加@符號)。定義一個新的Annotation類型與定義一個接口很像,例如:
public @interface Test{}
定義完該Annotation后,就可以在程序中使用該Annotation。使用Annotation,非常類似于public、final這樣的修飾符,通常,會把Annotation另放一行,并且放在所有修飾符之前。例如:
@Testpublic class MyClass{....}
1.1 成員變量
Annotation只有成員變量,沒有方法。**Annotation的成員變量在Annotation定義中以“無形參的方法”形式來聲明
,其方法名定義了該成員變量的名字,其返回值定義了該成員變量的類型。例如:
public @interface MyTag{ string name(); int age();}
示例中定義了2個成員變量,這2個成員變量以方法的形式來定義。
一旦在Annotation里定義了成員變量后,使用該Annotation時就應該為該Annotation的成員變量指定值。例如:
public class Test{ @MyTag(name="紅薯",age=30) public void info(){ ...... }}
也可以在定義Annotation的成員變量時,為其指定默認值
**,指定成員變量默認值使用default關鍵字。示例:
public @interface MyTag{ string name() default "我蘭"; int age() default 18;}
如果Annotation的成員變量已經指定了默認值,使用該Annotation時可以不為這些成員變量指定值,而是直接使用默認值。例如:
public class Test{ @MyTag public void info(){ ...... }}
根據Annotation是否包含成員變量,可以把Annotation分為如下兩類:
標記Annotation:沒有成員變量的Annotation被稱為標記。這種Annotation僅用自身的存在與否來為我們提供信息,例如@override等。
元數據Annotation:包含成員變量的Annotation。因為它們可以接受更多的元數據,因此被稱為元數據Annotation。
1.2 元注解
在定義Annotation時,也可以使用JDK提供的元注解來修飾Annotation定義。JDK提供了如下4個元注解(注解的注解,不是上述的”元數據Annotation“):
@Retention
@Target
@Documented
@Inherited
1.2.1 @Retention
@Retention用于指定Annotation可以保留多長時間。
@Retention包含一個名為“value”的成員變量,該value成員變量是RetentionPolicy枚舉類型。使用@Retention時,必須為其value指定值。value成員變量的值只能是如下3個:
RetentionPolicy.SOURCE:Annotation只保留在源代碼中,編譯器編譯時,直接丟棄這種Annotation。
RetentionPolicy.CLASS:編譯器把Annotation記錄在class文件中。當運行Java程序時,JVM中不再保留該Annotation。
RetentionPolicy.RUNTIME:編譯器把Annotation記錄在class文件中。當運行Java程序時,JVM會保留該Annotation,程序可以通過反射獲取該Annotation的信息。
示例:
package com.demo1;?import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;?//name=value形式//@Retention(value=RetentionPolicy.RUNTIME)?//直接指定@Retention(RetentionPolicy.RUNTIME)public @interface MyTag{ String name() default "我蘭";}
如果Annotation里有一個名為“value“的成員變量,使用該Annotation時,可以直接使用XXX(val)形式為value成員變量賦值,無須使用name=val形式。
1.2.2 @Target
@Target指定Annotation用于修飾哪些程序元素。@Target也包含一個名為”value“的成員變量,該value成員變量類型為ElementType[ ],ElementType為枚舉類型,值有如下幾個:
ElementType.TYPE:能修飾類、接口或枚舉類型
ElementType.FIELD:能修飾成員變量
ElementType.METHOD:能修飾方法
ElementType.PARAMETER:能修飾參數
ElementType.CONSTRUCTOR:能修飾構造器
ElementType.LOCAL_VARIABLE:能修飾局部變量
ElementType.ANNOTATION_TYPE:能修飾注解
ElementType.PACKAGE:能修飾包
示例1(單個ElementType):
package com.demo1;?import java.lang.annotation.ElementType;import java.lang.annotation.Target;?@Target(ElementType.FIELD)public @interface AnnTest { String name() default "sunchp";}
示例2(多個ElementType):
package com.demo1;?import java.lang.annotation.ElementType;import java.lang.annotation.Target;?@Target({ ElementType.FIELD, ElementType.METHOD })public @interface AnnTest { String name() default "sunchp";}
1.2.3 @Documented
如果定義注解A時,使用了@Documented修飾定義,則在用javadoc命令生成API文檔后,所有使用注解A修飾的程序元素,將會包含注解A的說明。
示例:
@Documentedpublic @interface Testable {}
public class Test { @Testable public void info() { }}
- {failImgCache = [];}if(failImgCache.indexOf(src) == -1 && src.trim().length){failImgCache.push(src);}$(this).closest('.md-image').addClass('md-img-error').removeClass('md-img-loaded'); " onload="var src = window.removeLastModifyQuery(this.getAttribute('src'));if(!src.trim()) return;if(loadedImgCache.indexOf(src) == -1 && src.trim().length){loadedImgCache.push(src);}$(this).closest('.md-image').addClass('md-img-loaded').removeClass('md-img-error');" style="box-sizing: border-box; border-width: 0px 4px 0px 2px; border-right-style: solid; border-left-style: solid; border-right-color: transparent; border-left-color: transparent; vertical-align: middle; max-width: 100%; cursor: default;">
1.2.4 @Inherited
@Inherited指定Annotation具有繼承性。
示例:
package com.demo2;?import java.lang.annotation.ElementType;import java.lang.annotation.Inherited;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;?@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Inheritedpublic @interface MyTag{?}
package com.demo2;?@MyTagpublic class Base {?}
package com.demo2;?//SubClass只是繼承了Base類//并未直接使用@MyTag注解修飾public class SubClass extends Base { public static void main(String[] args) { System.out.println(SubClass.class.isAnnotationPresent(MyTag.class)); }}
示例中Base使用@MyTag修飾,SubClass繼承Base,而且沒有直接使用@MyTag修飾,但是因為MyTag定義時,使用了@Inherited修飾,具有了繼承性,所以運行結果為true。
如果MyTag注解沒有被@Inherited修飾,則運行結果為:false。
1.3 基本Annotation
JDK默認提供了如下幾個基本Annotation:
**@Override **
限定重寫父類方法。對于子類中被@Override 修飾的方法,如果存在對應的被重寫的父類方法,則正確;如果不存在,則報錯。@Override 只能作用于方法,不能作用于其他程序元素。
@Deprecated
用于表示某個程序元素(類、方法等)已過時。如果使用被@Deprecated修飾的類或方法等,編譯器會發出警告。
@SuppressWarning
抑制編譯器警告。指示被@SuppressWarning修飾的程序元素(以及該程序元素中的所有子元素,例如類以及該類中的方法.....)取消顯示指定的編譯器警告。例如,常見的@SuppressWarning(value="unchecked")
@SafeVarargs
@SafeVarargs是JDK 7 專門為抑制“堆污染”警告提供的。
2 提取Annotation信息(反射)
當開發者使用了Annotation修飾了類、方法、Field等成員之后,這些Annotation不會自己生效,必須由開發者提供相應的代碼來提取并處理Annotation信息。這些處理提取和處理Annotation的代碼統稱為APT(Annotation Processing Tool)。
JDK主要提供了兩個類,來完成Annotation的提取:
java.lang.annotation.Annotation
接口:這個接口是所有Annotation類型的父接口(后面會分析Annotation的本質,Annotation本質是接口,而java.lang.annotation.Annotation
接口是這些接口的父接口)。
java.lang.reflect.AnnotatedElement
接口:該接口代表程序中可以被注解的程序元素。
2.1 java.lang.annotation.Annotation
java.lang.annotation.Annotation接口源碼:
package java.lang.annotation;?public interface Annotation {? boolean equals(Object obj);? int hashCode();? String toString();? Class<? extends Annotation> annotationType();}
java.lang.annotation.Annotation
接口的主要方法是annotationType( ),用于返回該注解的java.lang.Class。
2.2 java.lang.reflect.AnnotatedElement
java.lang.reflect.AnnotatedElement
接口源碼:
package java.lang.reflect;?import java.lang.annotation.Annotation;?public interface AnnotatedElement {? boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);? <T extends Annotation> T getAnnotation(Class<T> annotationClass);? Annotation[] getAnnotations();? Annotation[] getDeclaredAnnotations();}
主要方法有:
isAnnotationPresent(Class<? extends Annotation> annotationClass):判斷該程序元素上是否存在指定類型的注解,如果存在則返回true,否則返回false。
getAnnotation(Class<T> annotationClass):返回該程序元素上存在的指定類型的注解,如果該類型的注解不存在,則返回null
Annotation[] getAnnotations():返回該程序元素上存在的所有注解。
java.lang.reflect.AnnotatedElement
接口是所有程序元素(例如java.lang.Class
、java.lang.reflect.Method
、java.lang.reflect.Constructor
等)的父接口。類圖結構如下:
- {failImgCache = [];}if(failImgCache.indexOf(src) == -1 && src.trim().length){failImgCache.push(src);}$(this).closest('.md-image').addClass('md-img-error').removeClass('md-img-loaded'); " onload="var src = window.removeLastModifyQuery(this.getAttribute('src'));if(!src.trim()) return;if(loadedImgCache.indexOf(src) == -1 && src.trim().length){loadedImgCache.push(src);}$(this).closest('.md-image').addClass('md-img-loaded').removeClass('md-img-error');" style="box-sizing: border-box; border-width: 0px 4px 0px 2px; border-right-style: solid; border-left-style: solid; border-right-color: transparent; border-left-color: transparent; vertical-align: middle; max-width: 100%; cursor: default;">
所以程序通過反射獲取了某個類的AnnotatedElement對象(例如,A類method1()方法的java.lang.reflect.Method對象)后,就可以調用該對象的isAnnotationPresent( )、getAnnotation( )等方法來訪問注解信息。
為了獲取注解信息,必須使用反射知識。
PS:如果想要在運行時提取注解信息,在定義注解時,該注解必須使用@Retention(RetentionPolicy.RUNTIME)修飾。
2.3 示例
2.3.1 標記Annotation
給定一個類的全額限定名,加載類,并列出該類中被注解@MyTag修飾的方法和沒被修飾的方法。
注解定義:
package com.demo1;?import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;?@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyTag {?}
注解處理:
package com.demo1;?import java.lang.reflect.Method;?public class ProcessTool { public static void process(String clazz) { Class targetClass = null;? try { targetClass = Class.forName(clazz); } catch (ClassNotFoundException e) { e.printStackTrace(); }? for (Method m : targetClass.getMethods()) { if (m.isAnnotationPresent(MyTag.class)) { System.out.println("被MyTag注解修飾的方法名:" + m.getName()); } else { System.out.println("沒被MyTag注解修飾的方法名:" + m.getName()); } } }}
測試類:
package com.demo1;?public class Demo { public static void m1() {? }? @MyTag public static void m2() {? }}
package com.demo1;?public class Test {? public static void main(String[] args) { ProcessTool.process("com.demo1.Demo"); }}
運行結果:
沒被MyTag注解修飾的方法名:m1被MyTag注解修飾的方法名:m2沒被MyTag注解修飾的方法名:wait沒被MyTag注解修飾的方法名:wait沒被MyTag注解修飾的方法名:wait沒被MyTag注解修飾的方法名:equals沒被MyTag注解修飾的方法名:toString沒被MyTag注解修飾的方法名:hashCode沒被MyTag注解修飾的方法名:getClass沒被MyTag注解修飾的方法名:notify沒被MyTag注解修飾的方法名:notifyAll
2.3.2 元數據Annotation
給定一個類的全額限定名,加載類,找出被注解MyTag修飾的方法,并輸出每個方法的MyTag注解的屬性。
注解定義:
package com.demo1;?import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;?@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyTag { String name() default "我蘭";? int age() default 18;}
注解處理:
package com.demo1;?import java.lang.reflect.Method;?public class ProcessTool { public static void process(String clazz) { Class targetClass = null;? try { targetClass = Class.forName(clazz); } catch (ClassNotFoundException e) { e.printStackTrace(); }? for (Method m : targetClass.getMethods()) { if (m.isAnnotationPresent(MyTag.class)) { MyTag tag = m.getAnnotation(MyTag.class); System.out.println("方法" + m.getName() + "的MyTag注解內容為:" + tag.name() + "," + tag.age()); } } }}
測試類:
package com.demo1;?public class Demo { public static void m1() {? }? @MyTag public static void m2() {? }? @MyTag(name = "紅薯") public static void m3() {? }? @MyTag(name = "紅薯", age = 30) public static void m4() {? }}
package com.demo1;?public class Test {? public static void main(String[] args) { ProcessTool.process("com.demo1.Demo"); }}
運行結果:
方法m2的MyTag注解內容為:我蘭,18方法m3的MyTag注解內容為:紅薯,18方法m4的MyTag注解內容為:紅薯,30
若要獲取注解中的成員變量值,直接調用注解對象的"成員變量名( )"形式的方法就行,例如示例中的tag.name()等。
PS:在編譯器編譯注解定義時,自動在class文件中,添加與成員變量同名的抽象方法,用于反射時獲取成員變量的值。
通過上面的示例可以看出,其實Annotation十分簡單,它是對源代碼增加的一些特殊標記,這些特殊標記可通過反射獲取,當程序獲取這些特殊標記后,程序可以做出相應的處理(當然也可以完全忽略這些Annotation)。
3 注解本質
對于示例”2.3.2 元數據Annotation“中的MyTag注解,在編譯后,生成一個MyTag.class文件。反編譯該class文件:
javap -verbose -c MyTag.class > m.txt
MyTag注解的字節碼為: - {failImgCache = [];}if(failImgCache.indexOf(src) == -1 && src.trim().length){failImgCache.push(src);}$(this).closest('.md-image').addClass('md-img-error').removeClass('md-img-loaded'); " onload="var src = window.removeLastModifyQuery(this.getAttribute('src'));if(!src.trim()) return;if(loadedImgCache.indexOf(src) == -1 && src.trim().length){loadedImgCache.push(src);}$(this).closest('.md-image').addClass('md-img-loaded').removeClass('md-img-error');" style="box-sizing: border-box; border-width: 0px 4px 0px 2px; border-right-style: solid; border-left-style: solid; border-right-color: transparent; border-left-color: transparent; vertical-align: middle; max-width: 100%; cursor: default;">
通過分析字節碼可知:
**注解實質上會被編譯器編譯為接口
**,并且繼承java.lang.annotation.Annotation接口。
**注解的成員變量會被編譯器編譯為同名的抽象方法
**。
根據Java的class文件規范,class文件中會在程序元素的屬性位置記錄注解信息。例如,RuntimeVisibleAnnotations屬性位置,記錄修飾該類的注解有哪些;flags屬性位置,記錄該類是不是注解;在方法的AnnotationDefault屬性位置,記錄注解的成員變量默認值是多少。
我們再反編譯下示例”2.3.2 元數據Annotation“中的Demo測試類,查看下”被注解修飾的方法是怎樣記錄自己被注解修飾的“:
javap -verbose -c Demo.class > d.txt
反編譯結果如下:
- {failImgCache = [];}if(failImgCache.indexOf(src) == -1 && src.trim().length){failImgCache.push(src);}$(this).closest('.md-image').addClass('md-img-error').removeClass('md-img-loaded'); " onload="var src = window.removeLastModifyQuery(this.getAttribute('src'));if(!src.trim()) return;if(loadedImgCache.indexOf(src) == -1 && src.trim().length){loadedImgCache.push(src);}$(this).closest('.md-image').addClass('md-img-loaded').removeClass('md-img-error');" style="box-sizing: border-box; border-width: 0px 4px 0px 2px; border-right-style: solid; border-left-style: solid; border-right-color: transparent; border-left-color: transparent; vertical-align: middle; max-width: 100%; cursor: default;">
通過字節碼可知:
在字節碼文件中,每個方法都有RuntimeVisibleAnnotations屬性位置,用來放置注解和注解的成員變量賦值。JVM在解析class文件時,會解析RuntimeVisibleAnnotations屬性,并新建相應類型的注解對象,并將成員變量賦值。
如果要明白JVM對注解的運行機制,需要對class文件的格式規范有一定了解。(資料)
4 注解的意義
為編譯器提供輔助信息 — Annotations可以為編譯器提供而外信息,以便于檢測錯誤,抑制警告等.
編譯源代碼時進行而外操作 — 軟件工具可以通過處理Annotation信息來生成原代碼,xml文件等等.
運行時處理 — 有一些annotation甚至可以在程序運行時被檢測,使用.
總之,注解是一種元數據,起到了”描述,配置“的作用。
出處鏈接:
http://www.open-open.com/lib/view/open1423558996951.html
擴展閱讀
Java 注解Annotation
Java Annotation 及幾個常用開源項目注解原理簡析
Java自定義Annotation,通過反射解析Annotation
Java Annotation 學習筆記
Annotation實戰【自定義AbstractProcessor】