從JDK5開始,Java增加了Annotation(注解)摊崭,Annotation是代碼里的特殊標(biāo)記讼油,這些標(biāo)記可以在編譯、類加載呢簸、運(yùn)行時(shí)被讀取矮台,并執(zhí)行相應(yīng)的處理乏屯。通過使用Annotation,開發(fā)人員可以在不改變原有邏輯的情況下瘦赫,在源文件中嵌入一些補(bǔ)充的信息辰晕。代碼分析工具、開發(fā)工具和部署工具可以通過這些補(bǔ)充信息進(jìn)行驗(yàn)證确虱、處理或者進(jìn)行部署含友。
Annotation提供了一種為程序元素(包、類校辩、構(gòu)造器窘问、方法、成員變量宜咒、參數(shù)惠赫、局域變量)設(shè)置元數(shù)據(jù)的方法。Annotation不能運(yùn)行荧呐,它只有成員變量汉形,沒有方法。Annotation跟public倍阐、final等修飾符的地位一樣,都是程序元素的一部分逗威,Annotation不能作為一個(gè)程序元素使用峰搪。
1 定義Annotation
定義新的Annotation類型使用@interface關(guān)鍵字(在原有interface關(guān)鍵字前增加@符號)。定義一個(gè)新的Annotation類型與定義一個(gè)接口很像凯旭,例如:
public?@interface?Test{
}
定義完該Annotation后概耻,就可以在程序中使用該Annotation。使用Annotation罐呼,非常類似于public鞠柄、final這樣的修飾符,通常嫉柴,會把Annotation另放一行厌杜,并且放在所有修飾符之前。例如:
@Test
public?class?MyClass{
....
}
1.1 成員變量
Annotation只有成員變量计螺,沒有方法夯尽。Annotation的成員變量在Annotation定義中以“無形參的方法”形式來聲明,其方法名定義了該成員變量的名字登馒,其返回值定義了該成員變量的類型匙握。例如:
public?@interface?MyTag{
????string?name();
????int?age();
}
示例中定義了2個(gè)成員變量,這2個(gè)成員變量以方法的形式來定義陈轿。
一旦在Annotation里定義了成員變量后圈纺,使用該Annotation時(shí)就應(yīng)該為該Annotation的成員變量指定值秦忿。例如:
public?class?Test{
????@MyTag(name="紅薯",age=30)
????public?void?info(){
????......
????}
}
也可以在定義Annotation的成員變量時(shí)蛾娶,為其指定默認(rèn)值小渊,指定成員變量默認(rèn)值使用default關(guān)鍵字。示例:
public?@interface?MyTag{
????string?name()?default?"我蘭";
????int?age()?default?18;
}
如果Annotation的成員變量已經(jīng)指定了默認(rèn)值茫叭,使用該Annotation時(shí)可以不為這些成員變量指定值酬屉,而是直接使用默認(rèn)值。例如:
public?class?Test{
????@MyTag
????public?void?info(){
????......
????}
}
根據(jù)Annotation是否包含成員變量揍愁,可以把Annotation分為如下兩類:
標(biāo)記Annotation:沒有成員變量的Annotation被稱為標(biāo)記呐萨。這種Annotation僅用自身的存在與否來為我們提供信息,例如@override等莽囤。
元數(shù)據(jù)Annotation:包含成員變量的Annotation谬擦。因?yàn)樗鼈兛梢越邮芨嗟脑獢?shù)據(jù),因此被稱為元數(shù)據(jù)Annotation朽缎。
1.2 元注解
在定義Annotation時(shí)惨远,也可以使用JDK提供的元注解來修飾Annotation定義。JDK提供了如下4個(gè)元注解(注解的注解话肖,不是上述的”元數(shù)據(jù)Annotation“):
@Retention
@Target
@Documented
@Inherited
1.2.1 @Retention
@Retention用于指定Annotation可以保留多長時(shí)間北秽。
@Retention包含一個(gè)名為“value”的成員變量,該value成員變量是RetentionPolicy枚舉類型最筒。使用@Retention時(shí)贺氓,必須為其value指定值。value成員變量的值只能是如下3個(gè):
RetentionPolicy.SOURCE:Annotation只保留在源代碼中床蜘,編譯器編譯時(shí)辙培,直接丟棄這種Annotation。
RetentionPolicy.CLASS:編譯器把Annotation記錄在class文件中邢锯。當(dāng)運(yùn)行Java程序時(shí)扬蕊,JVM中不再保留該Annotation。
RetentionPolicy.RUNTIME:編譯器把Annotation記錄在class文件中丹擎。當(dāng)運(yùn)行Java程序時(shí)尾抑,JVM會保留該Annotation,程序可以通過反射獲取該Annotation的信息鸥鹉。
示例:
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里有一個(gè)名為“value“的成員變量蛮穿,使用該Annotation時(shí),可以直接使用XXX(val)形式為value成員變量賦值毁渗,無須使用name=val形式践磅。
1.2.2 @Target
@Target指定Annotation用于修飾哪些程序元素。@Target也包含一個(gè)名為”value“的成員變量灸异,該value成員變量類型為ElementType[ ]府适,ElementType為枚舉類型羔飞,值有如下幾個(gè):
ElementType.TYPE:能修飾類、接口或枚舉類型
ElementType.FIELD:能修飾成員變量
ElementType.METHOD:能修飾方法
ElementType.PARAMETER:能修飾參數(shù)
ElementType.CONSTRUCTOR:能修飾構(gòu)造器
ElementType.LOCAL_VARIABLE:能修飾局部變量
ElementType.ANNOTATION_TYPE:能修飾注解
ElementType.PACKAGE:能修飾包
示例1(單個(gè)ElementType):
import?java.lang.annotation.ElementType;
import?java.lang.annotation.Target;
@Target(ElementType.FIELD)
public?@interface?AnnTest?{
String?name()?default?"sunchp";
}
示例2(多個(gè)ElementType):
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時(shí)檐春,使用了@Documented修飾定義逻淌,則在用javadoc命令生成API文檔后,所有使用注解A修飾的程序元素疟暖,將會包含注解A的說明卡儒。
示例:
@Documented
public?@interface?Testable?{
}
public?class?Test?{
@Testable
public?void?info()?{
}
}
1.2.4 @Inherited
@Inherited指定Annotation具有繼承性。
示例:
package
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)
@Inherited
public?@interface?MyTag{
}
@MyTag
public?class?Base?{
}
//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修飾,但是因?yàn)镸yTag定義時(shí)欣舵,使用了@Inherited修飾擎鸠,具有了繼承性,所以運(yùn)行結(jié)果為true缘圈。
如果MyTag注解沒有被@Inherited修飾劣光,則運(yùn)行結(jié)果為:false。
1.3 基本Annotation
JDK默認(rèn)提供了如下幾個(gè)基本Annotation:
@Override?
限定重寫父類方法糟把。對于子類中被@Override 修飾的方法绢涡,如果存在對應(yīng)的被重寫的父類方法,則正確糊饱;如果不存在垂寥,則報(bào)錯(cuò)。@Override 只能作用于方法另锋,不能作用于其他程序元素。
@Deprecated
用于表示某個(gè)程序元素(類狭归、方法等)已過時(shí)夭坪。如果使用被@Deprecated修飾的類或方法等,編譯器會發(fā)出警告过椎。
@SuppressWarning
抑制編譯器警告室梅。指示被@SuppressWarning修飾的程序元素(以及該程序元素中的所有子元素,例如類以及該類中的方法.....)取消顯示指定的編譯器警告疚宇。例如亡鼠,常見的@SuppressWarning(value="unchecked")
@SafeVarargs
@SafeVarargs是JDK 7 專門為抑制“堆污染”警告提供的。
2 提取Annotation信息(反射)
當(dāng)開發(fā)者使用了Annotation修飾了類敷待、方法间涵、Field等成員之后,這些Annotation不會自己生效榜揖,必須由開發(fā)者提供相應(yīng)的代碼來提取并處理Annotation信息勾哩。這些處理提取和處理Annotation的代碼統(tǒng)稱為APT(Annotation Processing Tool)抗蠢。
JDK主要提供了兩個(gè)類,來完成Annotation的提人祭汀:
java.lang.annotation.Annotation接口:這個(gè)接口是所有Annotation類型的父接口(后面會分析Annotation的本質(zhì)迅矛,Annotation本質(zhì)是接口,而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?annotationType();
}
java.lang.annotation.Annotation接口的主要方法是annotationType( ),用于返回該注解的java.lang.Class威兜。
2.2java.lang.reflect.AnnotatedElement
java.lang.reflect.AnnotatedElement接口源碼:
package?java.lang.reflect;
import?java.lang.annotation.Annotation;
public?interface?AnnotatedElement?{
?????boolean?isAnnotationPresent(Class?annotationClass);
?????T?getAnnotation(Class?annotationClass);
????Annotation[]?getAnnotations();
????Annotation[]?getDeclaredAnnotations();
}
主要方法有:
isAnnotationPresent(Class annotationClass):判斷該程序元素上是否存在指定類型的注解销斟,如果存在則返回true,否則返回false牡属。
getAnnotation(Class annotationClass):返回該程序元素上存在的指定類型的注解票堵,如果該類型的注解不存在,則返回null
Annotation[] getAnnotations():返回該程序元素上存在的所有注解逮栅。
java.lang.reflect.AnnotatedElement接口是所有程序元素(例如java.lang.Class悴势、java.lang.reflect.Method、java.lang.reflect.Constructor等)的父接口措伐。類圖結(jié)構(gòu)如下:
所以程序通過反射獲取了某個(gè)類的AnnotatedElement對象(例如特纤,A類method1()方法的java.lang.reflect.Method對象)后,就可以調(diào)用該對象的isAnnotationPresent( )侥加、getAnnotation( )等方法來訪問注解信息捧存。
為了獲取注解信息,必須使用反射知識担败。
PS:如果想要在運(yùn)行時(shí)提取注解信息昔穴,在定義注解時(shí),該注解必須使用@Retention(RetentionPolicy.RUNTIME)修飾提前。
2.3 示例
2.3.1?標(biāo)記Annotation
給定一個(gè)類的全額限定名吗货,加載類,并列出該類中被注解@MyTag修飾的方法和沒被修飾的方法狈网。
注解定義:
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?{
}
注解處理:
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());
}
}
}
}
測試類:
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");
}
}
運(yùn)行結(jié)果:
沒被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?元數(shù)據(jù)Annotation
給定一個(gè)類的全額限定名宙搬,加載類,找出被注解MyTag修飾的方法拓哺,并輸出每個(gè)方法的MyTag注解的屬性勇垛。
注解定義:
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;
}
注解處理:
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注解內(nèi)容為:"?+?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");
}
}
運(yùn)行結(jié)果:
方法m2的MyTag注解內(nèi)容為:我蘭士鸥,18
方法m3的MyTag注解內(nèi)容為:紅薯闲孤,18
方法m4的MyTag注解內(nèi)容為:紅薯,30
若要獲取注解中的成員變量值础淤,直接調(diào)用注解對象的"成員變量民( )"形式的方法就行崭放,例如示例中的tag.name()等哨苛。
PS:在編譯器編譯注解定義時(shí),自動在class文件中币砂,添加與成員變量同名的抽象方法建峭,用于反射時(shí)獲取成員變量的值。
通過上面的示例可以看出决摧,其實(shí)Annotation十分簡單亿蒸,它是對源代碼增加的一些特殊標(biāo)記,這些特殊標(biāo)記可通過反射獲取掌桩,當(dāng)程序獲取這些特殊標(biāo)記后边锁,程序可以做出相應(yīng)的處理(當(dāng)然也可以完全忽略這些Annotation)。
3 注解本質(zhì)
對于示例”2.3.2 元數(shù)據(jù)Annotation“中的MyTag注解波岛,在編譯后茅坛,生成一個(gè)MyTag.class文件。反編譯該class文件:
javap?-verbose?-c?MyTag.class?>?m.txt
MyTag注解的字節(jié)碼為:
通過分析字節(jié)碼可知:
注解實(shí)質(zhì)上會被編譯器編譯為接口则拷,并且繼承java.lang.annotation.Annotation接口贡蓖。
注解的成員變量會被編譯器編譯為同名的抽象方法。
根據(jù)Java的class文件規(guī)范煌茬,class文件中會在程序元素的屬性位置記錄注解信息斥铺。例如,RuntimeVisibleAnnotations屬性位置坛善,記錄修飾該類的注解有哪些晾蜘;flags屬性位置,記錄該類是不是注解眠屎;在方法的AnnotationDefault屬性位置剔交,記錄注解的成員變量默認(rèn)值是多少。
我們再反編譯下示例”2.3.2 元數(shù)據(jù)Annotation“中的Demo測試類改衩,查看下”被注解修飾的方法是怎樣記錄自己被注解修飾的“:
javap?-verbose?-c?Demo.class?>?d.txt
反編譯結(jié)果如下:
通過字節(jié)碼可知:
在字節(jié)碼文件中省容,每個(gè)方法都有RuntimeVisibleAnnotations屬性位置,用來放置注解和注解的成員變量賦值燎字。JVM在解析class文件時(shí),會解析RuntimeVisibleAnnotations屬性阿宅,并新建相應(yīng)類型的注解對象候衍,并將成員變量賦值。
如果要明白JVM對注解的運(yùn)行機(jī)制洒放,需要對class文件的格式規(guī)范有一定了解蛉鹿。
4 注解的意義
為編譯器提供輔助信息 — Annotations可以為編譯器提供而外信息,以便于檢測錯(cuò)誤往湿,抑制警告等.
編譯源代碼時(shí)進(jìn)行而外操作 — 軟件工具可以通過處理Annotation信息來生成原代碼妖异,xml文件等等.
運(yùn)行時(shí)處理 — 有一些annotation甚至可以在程序運(yùn)行時(shí)被檢測惋戏,使用.
總之,注解是一種元數(shù)據(jù)他膳,起到了”描述响逢,配置“的作用。