本文講述Java Annotation的原理,如何自定義Java注解以及通過反射解析注解。
注解(Annotation)在JDK1.5之後增加的一個新特性,注解的引入意義很大,有很多非常有名的框架,比如Hibernate、Spring等框架中都大量使用注解。注解作為程序的元數據嵌入到程序。注解可以被解析工具或編譯工具解析,此處注意注解不同於注釋(comment)。
當一個接口直接繼承java.lang.annotation.Annotation接口時,仍是接口,而並非注解。要想自定義注解類型,只能通過@interface關鍵字的方式,其實通過該方式會隱含地繼承.Annotation接口。
所有與Annotation相關的API摘要如下:
(1). 注解類型(Annotation Types) API
(2). 枚舉(Enum) API
(3). 異常和錯誤 API
前面講到注解類型共4種,分別為Documented、Inherited、Retention、Target,接下來從jdk1.7的源碼角度,來分別加以說明:
源碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
@Documented:表示擁有該注解的元素可通過javadoc此類的工具進行文檔化。該類型應用於注解那些影響客戶使用帶注釋(comment)的元素聲明的類型。如果類型聲明是用Documented來注解的,這種類型的注解被作為被標注的程序成員的公共API。
例如,上面源碼@Retention的定義中有一行@Documented
,意思是指當前注解的元素會被javadoc工具進行文檔化,那麼在查看Java API文檔時可查看當該注解元素。
源碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
@Inherited:表示該注解類型被自動繼承,如果用戶在當前類中查詢這個元注解類型並且當前類的聲明中不包含這個元注解類型,那麼也將自動查詢當前類的父類是否存在Inherited元注解,這個動作將被重復執行知道這個標注類型被找到,或者是查詢到頂層的父類。
源碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
@Retention:表示該注解類型的注解保留的時長。當注解類型聲明中沒有@Retention元注解,則默認保留策略為RetentionPolicy.CLASS。關於保留策略(RetentionPolicy)是枚舉類型,共定義3種保留策略,如下表:
例如,上面源碼@Retention的定義中有一行@Retention(RetentionPolicy.RUNTIME)
,意思是指當前注解的保留策略為RUNTIME,即存在Java源文件,也存在經過編譯器編譯後的生成的Class字節碼文件,同時在運行時虛擬機(VM)中也保留該注解,可通過反射機制獲取當前注解內容。
源碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
@Target:表示該注解類型的所使用的程序元素類型。當注解類型聲明中沒有@Target元注解,則默認為可適用所有的程序元素。如果存在指定的@Target元注解,則編譯器強制實施相應的使用限制。關於程序元素(ElementType)是枚舉類型,共定義8種程序元素,如下表:
例如,上面源碼@Target的定義中有一行@Target(ElementType.ANNOTATION_TYPE)
,意思是指當前注解的元素類型是注解類型。
Java提供了多種內建的注解,下面接下幾個比較常用的注解:@Override、@Deprecated、@SuppressWarnings這3個注解。
源碼:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
用途:用於告知編譯器,我們需要覆寫超類的當前方法。如果某個方法帶有該注解但並沒有覆寫超類相應的方法,則編譯器會生成一條錯誤信息。
注解類型分析:@Override可適用元素為方法,僅僅保留在java源文件中。
源碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
用途:用於告知編譯器,某一程序元素(比如方法,成員變量)不建議使用時,應該使用這個注解。Java在javadoc中推薦使用該注解,一般應該提供為什麼該方法不推薦使用以及相應替代方法。
注解類型分析: @Deprecated可適合用於除注解類型聲明之外的所有元素,保留時長為運行時VM。
源碼:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
用於:用於告知編譯器忽略特定的警告信息,例在泛型中使用原生數據類型。
注解類型分析: @SuppressWarnings可適合用於除注解類型聲明和包名之外的所有元素,僅僅保留在java源文件中。
該注解有方法value(),可支持多個字符串參數,例如:
@SupressWarning(value={"uncheck","deprecation"})
前面講的@Override,@Deprecated都是無需參數的,而壓制警告是需要帶有參數的,可用參數如下:
3種內建注解的對比:
創建自定義注解,與創建接口有幾分相似,但注解需要以@開頭,下面先聲明一個自定義注解(AuthorAnno.java)文件:
package com.yuanhh.annotation;
import java.lang.annotation.Documented;
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;
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorAnno{
String name();
String website() default "gityuan.com";
int revision() default 1;
}
自定義注解規則:
name()
,website()
;default "gityuan.com"
,默認website=”gityuan.com”有了前面的自定義注解@AuthorAnno,那麼我們便可以在代碼中使用(AnnotationDemo.java),如下:
package com.yuanhh.annotation;
public class AnnotationDemo {
@AuthorAnno(name="yuanhh", website="gityuan.com", revision=1)
public static void main(String[] args) {
System.out.println("I am main method");
}
@SuppressWarnings({ "unchecked", "deprecation" })
@AuthorAnno(name="yuanhh", website="gityuan.com", revision=2)
public void demo(){
System.out.println("I am demo method");
}
}
由於該注解的保留策略為RetentionPolicy.RUNTIME,故可在運行期通過反射機制來使用,否則無法通過反射機制來獲取。
接下來,通過反射技術來解析自定義注解@AuthorAnno,關於反射類位於包java.lang.reflect,其中有一個接口AnnotatedElement
,該接口定義了注釋相關的幾個核心方法,如下:
前面自定義的注解,適用對象為Method。類Method繼承類AccessibleObject,而類AccessibleObject實現了AnnotatedElement接口,那麼可以利用上面的反射方法,來實現解析@AuthorAnno的功能(AnnotationParser.java),內容如下:
package com.yuanhh.annotation;
import java.lang.reflect.Method;
public class AnnotationParser {
public static void main(String[] args) throws SecurityException, ClassNotFoundException {
String clazz = "com.yuanhh.annotation.AnnotationDemo";
Method[] demoMethod = AnnotationParser.class
.getClassLoader().loadClass(clazz).getMethods();
for (Method method : demoMethod) {
if (method.isAnnotationPresent(AuthorAnno.class)) {
AuthorAnno authorInfo = method.getAnnotation(AuthorAnno.class);
System.out.println("method: "+ method);
System.out.println("name= "+ authorInfo.name() +
" , website= "+ authorInfo.website()
+ " , revision= "+authorInfo.revision());
}
}
}
}
程序運行的輸出結果:
method: public void com.yuanhh.annotation.AnnotationDemo.demo()
name= yuanhh , website= gityuan.com , revision= 2
method: public static void com.yuanhh.annotation.AnnotationDemo.main(java.lang.String[])
name= yuanhh , website= gityuan.com , revision= 1
這裡通過反射將注解直接輸出只是出於demo,完全可以根據拿到的注解信息做更多有意義的事。