本篇文章講述Java中注解的相關知識。從Java中內置的注解,到自定義注解,最后再介紹如何使用注解。
一、元素據
要想理解注解(Annotation)的作用,就要先理解Java中元數據的概念。
1.元數據概念
元數據是關于數據的數據。在編程語言上下文中,元數據是添加到程序元素如方法、字段、類和包上的額外信息。對數據進行說明描述的數據。
2.元數據的作用
一般來說,元數據可以用于創建文檔(根據程序元素上的注釋創建文檔),跟蹤代碼中的依賴性(可聲明方法是重載,依賴父類的方法),執行編譯時檢查(可聲明是否編譯期檢測),代碼分析。
如下:
1) 編寫文檔:通過代碼里標識的元數據生成文檔
2)代碼分析:通過代碼里標識的元數據對代碼進行分析
3)編譯檢查:通過代碼里標識的元數據讓編譯器能實現基本的編譯檢查
3.Java平臺元數據
注解Annotation就是java平臺的元數據,是 J2SE5.0新增加的功能,該機制允許在Java 代碼中添加自定義注釋,并允許通過反射(reflection),以編程方式訪問元數據注釋。通過提供為程序元素(類、方法等)附加額外數據的標準方法,元數據功能具有簡化和改進許多應用程序開發領域的潛在能力,其中包括配置管理、框架實現和代碼生成。
二、注解(Annotation)
1.注解(Annotation)的概念
注解(Annotation)在JDK1.5之后增加的一個新特性,注解的引入意義很大,有很多非常有名的框架,比如Hibernate、Spring等框架中都大量使用注解。注解作為程序的元數據嵌入到程序。注解可以被解析工具或編譯工具解析。
關于注解(Annotation)的作用,其實就是上述元數據的作用。
注意:Annotation能被用來為程序元素(類、方法、成員變量等)設置元素據。Annotaion不影響程序代碼的執行,無論增加、刪除Annotation,代碼都始終如一地執行。如果希望讓程序中的Annotation起一定的作用,只有通過解析工具或編譯工具對Annotation中的信息進行解析和處理。
2.內建注解
Java提供了多種內建的注解,下面接下幾個比較常用的注解:@Override、@Deprecated、@SuppressWarnings以及@FunctionalInterface這4個注解。內建注解主要實現了元數據的第二個作用:編譯檢查。
@Override
用途:用于告知編譯器,我們需要覆寫超類的當前方法。如果某個方法帶有該注解但并沒有覆寫超類相應的方法,則編譯器會生成一條錯誤信息。如果父類沒有這個要覆寫的方法,則編譯器也會生成一條錯誤信息。
@Override可適用元素為方法,僅僅保留在java源文件中。
@Deprecated
用途:使用這個注解,用于告知編譯器,某一程序元素(比如方法,成員變量)不建議使用了(即過時了)。
例如:
Person類中的info()方法使用@Deprecated表示該方法過時了。
public class Person {
@Deprecated
public void info(){
}
}
調用info()方法會編譯器會出現警告,告知該方法已過時。
注解類型分析: @Deprecated可適合用于除注解類型聲明之外的所有元素,保留時長為運行時。
@SuppressWarnings
用途:用于告知編譯器忽略特定的警告信息,例在泛型中使用原生數據類型,編譯器會發出警告,當使用該注解后,則不會發出警告。
注解類型分析: @SuppressWarnings可適合用于除注解類型聲明和包名之外的所有元素,僅僅保留在java源文件中。
該注解有方法value(),可支持多個字符串參數,用戶指定忽略哪種警告,例如:
@SupressWarning(value={"uncheck","deprecation"})
@FunctionalInterface
用途:用戶告知編譯器,檢查這個接口,保證該接口是函數式接口,即只能包含一個抽象方法,否則就會編譯出錯。
注解類型分析: @FunctionalInterface可適合用于注解類型聲明,保留時長為運行時。
3.元Annotation
JDK除了在java.lang提供了上述內建注解外,還在java.lang。annotation包下提供了6個Meta Annotation(元Annotataion),其中有5個元Annotation都用于修飾其他的Annotation定義。其中@Repeatable專門用戶定義Java 8 新增的可重復注解。
我們先介紹其中4個常用的修飾其他Annotation的元Annotation。在此之前,我們先了解如何自定義Annotation。
當一個接口直接繼承java.lang.annotation.Annotation接口時,仍是接口,而并非注解。要想自定義注解類型,只能通過@interface關鍵字的方式,其實通過該方式會隱含地繼承.Annotation接口。
@Documented
@Documented用戶指定被該元Annotation修飾的Annotation類將會被javadoc工具提取成文檔,如果定義Annotation類時使用了@Documented修飾,則所有使用該Annotation修飾的程序元素的API文檔中將會包含該Annotation說明。
例如:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
定義@Deprecated 時使用了@Documented,則任何元素使用@Deprecated修飾時,在生成API文檔時,將會包含 @Deprecated的說明
以下是String的一個過時的構造方法:
@Deprecated
public String(byte[] ascii,int hibyte,int offset, int count)
該注解實現了元數據的第一個功能:編寫文檔。
@Inherited
@Inherited指定被它修飾的Annotation將具有繼承性——如果某個類使用了@Xxx注解(定義該Annotation時使用了@Inherited修飾)修飾,則其子類將自動被@Xxx修飾。
@Retention
@Retention:表示該注解類型的注解保留的時長。當注解類型聲明中沒有@Retention元注解,則默認保留策略為RetentionPolicy.CLASS。關于保留策略(RetentionPolicy)是枚舉類型,共定義3種保留策略,如下表:
@Target
@Target:表示該注解類型的所適用的程序元素類型。當注解類型聲明中沒有@Target元注解,則默認為可適用所有的程序元素。如果存在指定的@Target元注解,則編譯器強制實施相應的使用限制。關于程序元素(ElementType)是枚舉類型,共定義8種程序元素,如下表:
三、自定義注解(Annotation)
創建自定義注解,與創建接口有幾分相似,但注解需要以@開頭。
@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotataion{
String name();
String website() default "hello";
int revision() default 1;
}
自定義注解中定義成員變量的規則:
其定義是以無形參的方法形式來聲明的。即:
注解方法不帶參數,比如name(),website();
注解方法返回值類型:基本類型、String、Enums、Annotation以及前面這些類型的數組類型
注解方法可有默認值,比如default "hello",默認website=”hello”
當然注解中也可以不存在成員變量,在使用解析注解進行操作時,僅以是否包含該注解來進行操作。當注解中有成員變量時,若沒有默認值,需要在使用注解時,指定成員變量的值。
public class AnnotationDemo {
@AuthorAnno(name="lvr", website="hello", revision=1)
public static void main(String[] args) {
System.out.println("I am main method");
}
@SuppressWarnings({ "unchecked", "deprecation" })
@AuthorAnno(name="lvr", website="hello", revision=2)
public void demo(){
System.out.println("I am demo method");
}
}
由于該注解的保留策略為RetentionPolicy.RUNTIME,故可在運行期通過反射機制來使用,否則無法通過反射機制來獲取。這時候注解實現的就是元數據的第二個作用:代碼分析。
下面來具體介紹如何通過反射機制來進行注解解析。
四、注解解析
接下來,通過反射技術來解析自定義注解。關于反射類位于包java.lang.reflect,其中有一個接口AnnotatedElement,該接口主要有如下幾個實現類:Class,Constructor,Field,Method,Package。除此之外,該接口定義了注釋相關的幾個核心方法,如下:
因此,當獲取了某個類的Class對象,然后獲取其Field,Method等對象,通過上述4個方法提取其中的注解,然后獲得注解的詳細信息。
public class AnnotationParser {
public static void main(String[] args) throws SecurityException, ClassNotFoundException {
String clazz = "com.lvr.annotation.AnnotationDemo";
Method[] demoMethod = AnnotationParser.class
.getClassLoader().loadClass(clazz).getMethods();
for (Method method : demoMethod) {
if (method.isAnnotationPresent(MyAnnotataion.class)) {
MyAnnotataion annotationInfo = method.getAnnotation(MyAnnotataion.class);
System.out.println("method: "+ method);
System.out.println("name= "+ annotationInfo.name() +
" , website= "+ annotationInfo.website()
+ " , revision= "+annotationInfo.revision());
}
}
}
}
以上僅是一個示例,其實可以根據拿到的注解信息做更多有意義的事。