注解的本質
「java.lang.annotation.Annotation」接口中有這么一句話,用來描述『注解』。
The common interface extended by all annotation types
所有的注解類型都繼承自這個普通的接口(Annotation)
這句話有點抽象,但卻說出了注解的本質。我們看一個 JDK 內置注解的定義:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
這是注解 @Override 的定義,其實它本質上就是:
public interface Override extends Annotation{ }
沒錯,注解的本質就是一個繼承了 Annotation 接口的接口。有關這一點,你可以去反編譯任意一個注解類,你會得到結果的。
一個注解准確意義上來說,只不過是一種特殊的注釋而已,如果沒有解析它的代碼,它可能連注釋都不如。
而解析一個類或者方法的注解往往有兩種形式,一種是編譯期直接的掃描,一種是運行期反射。反射的事情我們待會說,而編譯器的掃描指的是編譯器在對 java 代碼編譯字節碼的過程中會檢測到某個類或者方法被一些注解修飾,這時它就會對於這些注解進行某些處理。
典型的就是注解 @Override,一旦編譯器檢測到某個方法被修飾了 @Override 注解,編譯器就會檢查當前方法的方法簽名是否真正重寫了父類的某個方法,也就是比較父類中是否具有一個同樣的方法簽名。
這一種情況只適用於那些編譯器已經熟知的注解類,比如 JDK 內置的幾個注解,而你自定義的注解,編譯器是不知道你這個注解的作用的,當然也不知道該如何處理,往往只是會根據該注解的作用范圍來選擇是否編譯進字節碼文件,僅此而已。
元注解
『元注解』是用於修飾注解的注解,通常用在注解的定義上,例如:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
這是我們 @Override 注解的定義,你可以看到其中的 @Target,@Retention 兩個注解就是我們所謂的『元注解』,『元注解』一般用於指定某個注解生命周期以及作用目標等信息。
JAVA 中有以下幾個『元注解』:
- @Target:注解的作用目標
- @Retention:注解的生命周期
- @Documented:注解是否應當被包含在 JavaDoc 文檔中
- @Inherited:是否允許子類繼承該注解
其中,@Target 用於指明被修飾的注解最終可以作用的目標是誰,也就是指明,你的注解到底是用來修飾方法的?修飾類的?還是用來修飾字段屬性的。
@Target 的定義如下:

我們可以通過以下的方式來為這個 value 傳值:
@Target(value = {ElementType.FIELD})
被這個 @Target 注解修飾的注解將只能作用在成員字段上,不能用於修飾方法或者類。其中,ElementType 是一個枚舉類型,有以下一些值:
- ElementType.TYPE:允許被修飾的注解作用在類、接口和枚舉上
- ElementType.FIELD:允許作用在屬性字段上
- ElementType.METHOD:允許作用在方法上
- ElementType.PARAMETER:允許作用在方法參數上
- ElementType.CONSTRUCTOR:允許作用在構造器上
- ElementType.LOCAL_VARIABLE:允許作用在本地局部變量上
- ElementType.ANNOTATION_TYPE:允許作用在注解上
- ElementType.PACKAGE:允許作用在包上
@Retention 用於指明當前注解的生命周期,它的基本定義如下:

同樣的,它也有一個 value 屬性:
@Retention(value = RetentionPolicy.RUNTIME
這里的 RetentionPolicy 依然是一個枚舉類型,它有以下幾個枚舉值可取:
- RetentionPolicy.SOURCE:當前注解編譯期可見,不會寫入 class 文件
- RetentionPolicy.CLASS:類加載階段丟棄,會寫入 class 文件
- RetentionPolicy.RUNTIME:永久保存,可以反射獲取
@Retention 注解指定了被修飾的注解的生命周期,一種是只能在編譯期可見,編譯后會被丟棄,一種會被編譯器編譯進 class 文件中,無論是類或是方法,乃至字段,他們都是有屬性表的,而 JAVA 虛擬機也定義了幾種注解屬性表用於存儲注解信息,但是這種可見性不能帶到方法區,類加載時會予以丟棄,最后一種則是永久存在的可見性。
剩下兩種類型的注解我們日常用的不多,也比較簡單,這里不再詳細的進行介紹了,你只需要知道他們各自的作用即可。@Documented 注解修飾的注解,當我們執行 JavaDoc 文檔打包時會被保存進 doc 文檔,反之將在打包時丟棄。@Inherited 注解修飾的注解是具有可繼承性的,也就說我們的注解修飾了一個類,而該類的子類將自動繼承父類的該注解。
JAVA 的內置三大注解
除了上述四種元注解外,JDK 還為我們預定義了另外三種注解,它們是:
- @Override
- @Deprecated
- @SuppressWarnings
@Override 注解想必是大家很熟悉的了,它的定義如下:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
它沒有任何的屬性,所以並不能存儲任何其他信息。它只能作用於方法之上,編譯結束后將被丟棄。
所以你看,它就是一種典型的『標記式注解』,僅被編譯器可知,編譯器在對 java 文件進行編譯成字節碼的過程中,一旦檢測到某個方法上被修飾了該注解,就會去匹對父類中是否具有一個同樣方法簽名的函數,如果不是,自然不能通過編譯。
@Deprecated 的基本定義如下:

依然是一種『標記式注解』,永久存在,可以修飾所有的類型,作用是,標記當前的類或者方法或者字段等已經不再被推薦使用了,可能下一次的 JDK 版本就會刪除。
當然,編譯器並不會強制要求你做什么,只是告訴你 JDK 已經不再推薦使用當前的方法或者類了,建議你使用某個替代者。
@SuppressWarnings 主要用來壓制 java 的警告,它的基本定義如下:

它有一個 value 屬性需要你主動的傳值,這個 value 代表一個什么意思呢,這個 value 代表的就是需要被壓制的警告類型。例如:
public static void main(String[] args) { Date date = new Date(2018, 7, 11); }
這么一段代碼,程序啟動時編譯器會報一個警告。
Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已過時
而如果我們不希望程序啟動時,編譯器檢查代碼中過時的方法,就可以使用 @SuppressWarnings 注解並給它的 value 屬性傳入一個參數值來壓制編譯器的檢查。
@SuppressWarning(value = "deprecated") public static void main(String[] args) { Date date = new Date(2018, 7, 11); }
這樣你就會發現,編譯器不再檢查 main 方法下是否有過時的方法調用,也就壓制了編譯器對於這種警告的檢查。
當然,JAVA 中還有很多的警告類型,他們都會對應一個字符串,通過設置 value 屬性的值即可壓制對於這一類警告類型的檢查。
自定義注解的相關內容就不再贅述了,比較簡單,通過類似以下的語法即可自定義一個注解。
public @interface InnotationName{ }
當然,自定義注解的時候也可以選擇性的使用元注解進行修飾,這樣你可以更加具體的指定你的注解的生命周期、作用范圍等信息。
注解的內容
在上面的注解源碼中可以看到有的注解中沒有任何內容,有的注解的有內容,看似像方法。
注解的內容的語法格式:
數據類型 屬性名() default 默認值,數據類型用於描述屬性的數據類型,默認值是說當沒有給屬性賦值時使用默認值,一般String使用空字符串""作為默認值,數組一般使用空數組{ }作為默認值.
下面看一下SpringMVC中的RequestMapping的注解的聲明
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
注解和反射基本API
// 獲取某個類型的注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
// 獲取所有注解(包括父類中被Inherited修飾的注解)
public Annotation[] getAnnotations();
// 獲取聲明的注解(但是不包括父類中被Inherited修飾的注解)
public Annotation[] getDeclaredAnnotations();
// 判斷某個對象上是否被某個注解進行標注
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 獲取某個類聲明的所有字段
public Field[] getDeclaredFields() throws SecurityException;
// 獲取某個方法
public Method getMethod(String name, Class<?>... parameterTypes);
使用自定義注解可以在切面編程中使用,處理異常等