注解知識掃盲
一、注解
1.1 概述
注解(Annotation)在JDK1.5之后增加的一個新特性,注解的引入意義很大,有很多非常有名的框架,比如Hibernate、Spring等框架中都大量使用注解。注解作為程序的元數(shù)據(jù)嵌入到程序。注解可以被解析工具或編譯工具解析,此處注意注解不同于注釋(comment)。
當(dāng)一個接口直接繼承java.lang.annotation.Annotation接口時,仍是接口,而并非注解。要想自定義注解類型,只能通過@interface關(guān)鍵字的方式,其實通過該方式會隱含地繼承.Annotation接口。
1.2 API 摘要
所有與Annotation相關(guān)的API摘要如下:
(1). 注解類型(Annotation Types) API
| 注解類型 | 含義 |
|---|---|
| Documented | 表示含有該注解類型的元素(帶有注釋的)會通過javadoc或類似工具進(jìn)行文檔化 |
| Inherited | 表示注解類型能被自動繼承 |
| Retention | 表示注解類型的存活時長 |
| Target | 表示注解類型所適用的程序元素的種類 |
(2). 枚舉(Enum) API
| 枚舉 | 含義 |
|---|---|
| ElementType | 程序元素類型,用于Target注解類型 |
| RetentionPolicy | 注解保留策略,用于Retention注解類型 |
(3). 異常和錯誤 API
| 異常/錯誤 | 含義 |
|---|---|
| AnnotationTypeMismatchException | 當(dāng)注解經(jīng)過編譯(或序列化)后,注解類型改變的情況下,程序視圖訪問該注解所對應(yīng)的元素,則拋出此異常 |
| IncompleteAnnotationException | 當(dāng)注解經(jīng)過編譯(或序列化)后,將其添加到注解類型定義的情況下,程序視圖訪問該注解所對應(yīng)的元素,則拋出此異常。 |
| AnnotationFormatError | 當(dāng)注解解析器試圖從類文件中讀取注解并確定注解出現(xiàn)異常時,拋出該錯誤 |
二、注解類型
前面講到注解類型共4種,分別為Documented、Inherited、Retention、Target,接下來從jdk1.7的源碼角度,來分別加以說明:
2.1 Documented
源碼:
1@Documented
2@Retention(RetentionPolicy.RUNTIME)
3@Target(ElementType.ANNOTATION_TYPE)
4public?@interface?Documented?{
5}@Documented:表示擁有該注解的元素可通過javadoc此類的工具進(jìn)行文檔化。該類型應(yīng)用于注解那些影響客戶使用帶注釋(comment)的元素聲明的類型。如果類型聲明是用Documented來注解的,這種類型的注解被作為被標(biāo)注的程序成員的公共API。
例如,上面源碼@Retention的定義中有一行@Documented,意思是指當(dāng)前注解的元素會被javadoc工具進(jìn)行文檔化,那么在查看Java API文檔時可查看該注解元素。
2.2 Inherited
源碼:
1@Documented
2@Retention(RetentionPolicy.RUNTIME)
3@Target(ElementType.ANNOTATION_TYPE)
4public?@interface?Inherited?{
5}@Inherited:表示該注解類型被自動繼承,如果用戶在當(dāng)前類中查詢這個元注解類型并且當(dāng)前類的聲明中不包含這個元注解類型,那么也將自動查詢當(dāng)前類的父類是否存在Inherited元注解,這個動作將被重復(fù)執(zhí)行知道這個標(biāo)注類型被找到,或者是查詢到頂層的父類。
2.3 Retention
源碼:
1@Documented
2@Retention(RetentionPolicy.RUNTIME)
3@Target(ElementType.ANNOTATION_TYPE)
4public?@interface?Retention?{
5????RetentionPolicy?value();
6}@Retention:表示該注解類型的注解保留的時長。當(dāng)注解類型聲明中沒有@Retention元注解,則默認(rèn)保留策略為RetentionPolicy.CLASS。關(guān)于保留策略(RetentionPolicy)是枚舉類型,共定義3種保留策略,如下表:
| RetentionPolicy | 含義 |
|---|---|
| SOURCE | 僅存在Java源文件,經(jīng)過編譯器后便丟棄相應(yīng)的注解 |
| CLASS | 存在Java源文件,以及經(jīng)編譯器后生成的Class字節(jié)碼文件,但在運(yùn)行時VM不再保留注釋 |
| RUNTIME | 存在源文件、編譯生成的Class字節(jié)碼文件,以及保留在運(yùn)行時VM中,可通過反射性地讀取注解 |
例如,上面源碼@Retention的定義中有一行@Retention(RetentionPolicy.RUNTIME),意思是指當(dāng)前注解的保留策略為RUNTIME,即存在Java源文件,也存在經(jīng)過編譯器編譯后的生成的Class字節(jié)碼文件,同時在運(yùn)行時虛擬機(jī)(VM)中也保留該注解,可通過反射機(jī)制獲取當(dāng)前注解內(nèi)容。
2.4 Target
源碼:
1@Documented
2@Retention(RetentionPolicy.RUNTIME)
3@Target(ElementType.ANNOTATION_TYPE)
4public?@interface?Target?{
5????ElementType[]?value();
6}@Target:表示該注解類型的所使用的程序元素類型。當(dāng)注解類型聲明中沒有@Target元注解,則默認(rèn)為可適用所有的程序元素。如果存在指定的@Target元注解,則編譯器強(qiáng)制實施相應(yīng)的使用限制。關(guān)于程序元素(ElementType)是枚舉類型,共定義8種程序元素,如下表:
| ElementType | 含義 |
|---|---|
| ANNOTATION_TYPE | 注解類型聲明 |
| CONSTRUCTOR | 構(gòu)造方法聲明 |
| FIELD | 字段聲明(包括枚舉常量) |
| LOCAL_VARIABLE | 局部變量聲明 |
| METHOD | 方法聲明 |
| PACKAGE | 包聲明 |
| PARAMETER | 參數(shù)聲明 |
| TYPE | 類、接口(包括注解類型)或枚舉聲明 |
例如,上面源碼@Target的定義中有一行@Target(ElementType.ANNOTATION_TYPE),意思是指當(dāng)前注解的元素類型是注解類型。
三、內(nèi)建注解
Java提供了多種內(nèi)建的注解,下面接下幾個比較常用的注解:@Override、@Deprecated、@SuppressWarnings這3個注解。
3.1 @Override(覆寫)
源碼:
1@Target(ElementType.METHOD)
2@Retention(RetentionPolicy.SOURCE)
3public?@interface?Override?{
4}用途:用于告知編譯器,我們需要覆寫超類的當(dāng)前方法。如果某個方法帶有該注解但并沒有覆寫超類相應(yīng)的方法,則編譯器會生成一條錯誤信息。
注解類型分析:@Override可適用元素為方法,僅僅保留在java源文件中。
3.2 @Deprecated(不贊成使用)
源碼:
1@Documented
2@Retention(RetentionPolicy.RUNTIME)
3@Target(value={CONSTRUCTOR,?FIELD,?LOCAL_VARIABLE,?METHOD,?PACKAGE,?PARAMETER,?TYPE})
4public?@interface?Deprecated?{
5}用途:用于告知編譯器,某一程序元素(比如方法,成員變量)不建議使用時,應(yīng)該使用這個注解。Java在javadoc中推薦使用該注解,一般應(yīng)該提供為什么該方法不推薦使用以及相應(yīng)替代方法。
注解類型分析:@Deprecated可適合用于除注解類型聲明之外的所有元素,保留時長為運(yùn)行時VM。
3.3 @SuppressWarnings(壓制警告)
源碼:
1@Target({TYPE,?FIELD,?METHOD,?PARAMETER,?CONSTRUCTOR,?LOCAL_VARIABLE})
2@Retention(RetentionPolicy.SOURCE)
3public?@interface?SuppressWarnings?{
4????String[]?value();
5}用于:用于告知編譯器忽略特定的警告信息,例在泛型中使用原生數(shù)據(jù)類型。
注解類型分析:@SuppressWarnings可適合用于除注解類型聲明和包名之外的所有元素,僅僅保留在java源文件中。
該注解有方法value(),可支持多個字符串參數(shù),例如:
1@SupressWarning(value={"uncheck","deprecation"})?前面講的@Override,@Deprecated都是無需參數(shù)的,而壓制警告是需要帶有參數(shù)的,可用參數(shù)如下:
| 參數(shù) | 含義 |
|---|---|
| deprecation | 使用了過時的類或方法時的警告 |
| unchecked | 執(zhí)行了未檢查的轉(zhuǎn)換時的警告 |
| fallthrough | 當(dāng)Switch程序塊進(jìn)入進(jìn)入下一個case而沒有Break時的警告 |
| path | 在類路徑、源文件路徑等有不存在路徑時的警告 |
| serial | 當(dāng)可序列化的類缺少serialVersionUID定義時的警告 |
| finally | 任意finally子句不能正常完成時的警告 |
| all | 以上所有情況的警告 |
3.4 對比
3種內(nèi)建注解的對比:
| 內(nèi)建注解 | Target | Retention |
|---|---|---|
| Override | METHOD | SOURCE |
| SuppressWarnings | 除ANNOTATION_TYPE和PACKAGE外的所有 | SOURCE |
| Deprecated | 除ANNOTATION_TYPE外的所有 | RUNTIME |
四、 實戰(zhàn)
4.1 自定義注解
創(chuàng)建自定義注解,與創(chuàng)建接口有幾分相似,但注解需要以@開頭,下面先聲明一個自定義注解(AuthorAnno.java)文件:
1package?com.test.annotation;
2import?java.lang.annotation.Documented;
3import?java.lang.annotation.ElementType;
4import?java.lang.annotation.Inherited;
5import?java.lang.annotation.Retention;
6import?java.lang.annotation.RetentionPolicy;
7import?java.lang.annotation.Target;
8
9@Documented
10@Target(ElementType.METHOD)
11@Inherited
12@Retention(RetentionPolicy.RUNTIME)
13public?@interface?AuthorAnno{
14????String?name();
15????String?website()?default?"gityuan.com";
16????int?revision()?default?1;
17}
自定義注解規(guī)則:
注解方法不帶參數(shù),比如
name(),website();注解方法返回值類型:基本類型、String、Enums、Annotation以及前面這些類型的數(shù)組類型
注解方法可有默認(rèn)值,比如
default "gityuan.com",默認(rèn)website=”gityuan.com”
有了前面的自定義注解@AuthorAnno,那么我們便可以在代碼中使用(AnnotationDemo.java),如下:
1package?com.test.annotation;
2
3public?class?AnnotationDemo?{
4????@AuthorAnno(name="yuanhh",?website="gityuan.com",?revision=1)
5????public?static?void?main(String[]?args)?{
6????????System.out.println("I?am?main?method");
7????}
8
9????@SuppressWarnings({?"unchecked",?"deprecation"?})
10????@AuthorAnno(name="yuanhh",?website="gityuan.com",?revision=2)
11????public?void?demo(){
12????????System.out.println("I?am?demo?method");
13????}
14}由于該注解的保留策略為RetentionPolicy.RUNTIME,故可在運(yùn)行期通過反射機(jī)制來使用,否則無法通過反射機(jī)制來獲取。
4.2 注解解析
接下來,通過反射技術(shù)來解析自定義注解@AuthorAnno,關(guān)于反射類位于包java.lang.reflect,其中有一個接口AnnotatedElement,該接口定義了注釋相關(guān)的幾個核心方法,如下:
| 返回值 | 方法 | 解釋 |
|---|---|---|
| getAnnotation(Class | 當(dāng)存在該元素的指定類型注解,則返回相應(yīng)注釋,否則返回null | |
| Annotation[] | getAnnotations() | 返回此元素上存在的所有注解 |
| Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解。 |
| boolean | isAnnotationPresent(Class extends Annotation> annotationClass) | 當(dāng)存在該元素的指定類型注解,則返回true,否則返回false |
前面自定義的注解,適用對象為Method。類Method繼承類AccessibleObject,而類AccessibleObject實現(xiàn)了AnnotatedElement接口,那么可以利用上面的反射方法,來實現(xiàn)解析@AuthorAnno的功能(AnnotationParser.java),內(nèi)容如下:
1package?com.test.annotation;
2import?java.lang.reflect.Method;
3
4public?class?AnnotationParser?{
5????public?static?void?main(String[]?args)?throws?SecurityException,?ClassNotFoundException?{
6????????String?clazz?=?"com.yuanhh.annotation.AnnotationDemo";
7????????Method[]??demoMethod?=?AnnotationParser.class
8????????????????.getClassLoader().loadClass(clazz).getMethods();
9
10????????for?(Method?method?:?demoMethod)?{
11????????????if?(method.isAnnotationPresent(AuthorAnno.class))?{
12?????????????????AuthorAnno?authorInfo?=?method.getAnnotation(AuthorAnno.class);
13?????????????????System.out.println("method:?"+?method);
14?????????????????System.out.println("name=?"+?authorInfo.name()?+?
15?????????????????????????"?,?website=?"+?authorInfo.website()
16????????????????????????+?"?,?revision=?"+authorInfo.revision());
17????????????}
18????????}
19????}
20}
程序運(yùn)行的輸出結(jié)果:
1method:?public?void?com.yuanhh.annotation.AnnotationDemo.demo()
2name=?yuanhh?,?website=?gityuan.com?,?revision=?2
3method:?public?static?void?com.yuanhh.annotation.AnnotationDemo.main(java.lang.String[])
4name=?yuanhh?,?website=?gityuan.com?,?revision=?1這里通過反射將注解直接輸出只是出于demo,完全可以根據(jù)拿到的注解信息做更多有意義的事。
source: //yuanfentiank789.github.io/2016/01/23/java-annotation

喜歡,在看
