程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java注解(Annotation)詳解

Java注解(Annotation)詳解

編輯:關於JAVA
 

本文講述Java Annotation的原理,如何自定義Java注解以及通過反射解析注解。

一、注解

1.1 概述

注解(Annotation)在JDK1.5之後增加的一個新特性,注解的引入意義很大,有很多非常有名的框架,比如Hibernate、Spring等框架中都大量使用注解。注解作為程序的元數據嵌入到程序。注解可以被解析工具或編譯工具解析,此處注意注解不同於注釋(comment)。

當一個接口直接繼承java.lang.annotation.Annotation接口時,仍是接口,而並非注解。要想自定義注解類型,只能通過@interface關鍵字的方式,其實通過該方式會隱含地繼承.Annotation接口。

1.2 API 摘要

所有與Annotation相關的API摘要如下:

(1). 注解類型(Annotation Types) API

注解類型 含義 Documented 表示含有該注解類型的元素(帶有注釋的)會通過javadoc或類似工具進行文檔化 Inherited 表示注解類型能被自動繼承 Retention 表示注解類型的存活時長 Target 表示注解類型所適用的程序元素的種類

(2). 枚舉(Enum) API

枚舉 含義 ElementType 程序元素類型,用於Target注解類型 RetentionPolicy 注解保留策略,用於Retention注解類型

(3). 異常和錯誤 API

異常/錯誤 含義 AnnotationTypeMismatchException 當注解經過編譯(或序列化)後,注解類型改變的情況下,程序視圖訪問該注解所對應的元素,則拋出此異常 IncompleteAnnotationException 當注解經過編譯(或序列化)後,將其添加到注解類型定義的情況下,程序視圖訪問該注解所對應的元素,則拋出此異常。 AnnotationFormatError 當注解解析器試圖從類文件中讀取注解並確定注解出現異常時,拋出該錯誤

二、注解類型

前面講到注解類型共4種,分別為Documented、Inherited、Retention、Target,接下來從jdk1.7的源碼角度,來分別加以說明:

2.1 Documented

源碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

@Documented:表示擁有該注解的元素可通過javadoc此類的工具進行文檔化。該類型應用於注解那些影響客戶使用帶注釋(comment)的元素聲明的類型。如果類型聲明是用Documented來注解的,這種類型的注解被作為被標注的程序成員的公共API。

例如,上面源碼@Retention的定義中有一行@Documented,意思是指當前注解的元素會被javadoc工具進行文檔化,那麼在查看Java API文檔時可查看當該注解元素。

2.2 Inherited

源碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

@Inherited:表示該注解類型被自動繼承,如果用戶在當前類中查詢這個元注解類型並且當前類的聲明中不包含這個元注解類型,那麼也將自動查詢當前類的父類是否存在Inherited元注解,這個動作將被重復執行知道這個標注類型被找到,或者是查詢到頂層的父類。

2.3 Retention

源碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

@Retention:表示該注解類型的注解保留的時長。當注解類型聲明中沒有@Retention元注解,則默認保留策略為RetentionPolicy.CLASS。關於保留策略(RetentionPolicy)是枚舉類型,共定義3種保留策略,如下表:

RetentionPolicy 含義 SOURCE 僅存在Java源文件,經過編譯器後便丟棄相應的注解 CLASS 存在Java源文件,以及經編譯器後生成的Class字節碼文件,但在運行時VM不再保留注釋 RUNTIME 存在源文件、編譯生成的Class字節碼文件,以及保留在運行時VM中,可通過反射性地讀取注解

例如,上面源碼@Retention的定義中有一行@Retention(RetentionPolicy.RUNTIME),意思是指當前注解的保留策略為RUNTIME,即存在Java源文件,也存在經過編譯器編譯後的生成的Class字節碼文件,同時在運行時虛擬機(VM)中也保留該注解,可通過反射機制獲取當前注解內容。

2.4 Target

源碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

@Target:表示該注解類型的所使用的程序元素類型。當注解類型聲明中沒有@Target元注解,則默認為可適用所有的程序元素。如果存在指定的@Target元注解,則編譯器強制實施相應的使用限制。關於程序元素(ElementType)是枚舉類型,共定義8種程序元素,如下表:

ElementType 含義 ANNOTATION_TYPE 注解類型聲明 CONSTRUCTOR 構造方法聲明 FIELD 字段聲明(包括枚舉常量) LOCAL_VARIABLE 局部變量聲明 METHOD 方法聲明 PACKAGE 包聲明 PARAMETER 參數聲明 TYPE 類、接口(包括注解類型)或枚舉聲明

例如,上面源碼@Target的定義中有一行@Target(ElementType.ANNOTATION_TYPE),意思是指當前注解的元素類型是注解類型。

三、內建注解

Java提供了多種內建的注解,下面接下幾個比較常用的注解:@Override、@Deprecated、@SuppressWarnings這3個注解。

3.1 @Override(覆寫)

源碼:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

用途:用於告知編譯器,我們需要覆寫超類的當前方法。如果某個方法帶有該注解但並沒有覆寫超類相應的方法,則編譯器會生成一條錯誤信息。

注解類型分析:@Override可適用元素為方法,僅僅保留在java源文件中。

3.2 @Deprecated(不贊成使用)

源碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

用途:用於告知編譯器,某一程序元素(比如方法,成員變量)不建議使用時,應該使用這個注解。Java在javadoc中推薦使用該注解,一般應該提供為什麼該方法不推薦使用以及相應替代方法。

注解類型分析: @Deprecated可適合用於除注解類型聲明之外的所有元素,保留時長為運行時VM。

3.3 @SuppressWarnings(壓制警告)

源碼:

@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都是無需參數的,而壓制警告是需要帶有參數的,可用參數如下:

參數 含義 deprecation 使用了過時的類或方法時的警告 unchecked 執行了未檢查的轉換時的警告 fallthrough 當Switch程序塊進入進入下一個case而沒有Break時的警告 path 在類路徑、源文件路徑等有不存在路徑時的警告 serial 當可序列化的類缺少serialVersionUID定義時的警告 finally 任意finally子句不能正常完成時的警告 all 以上所有情況的警告

3.4 對比

3種內建注解的對比:

內建注解 Target Retention Override METHOD SOURCE SuppressWarnings 除ANNOTATION_TYPE和PACKAGE外的所有 SOURCE Deprecated 除ANNOTATION_TYPE外的所有 RUNTIME

四、 實戰

4.1 自定義注解

創建自定義注解,與創建接口有幾分相似,但注解需要以@開頭,下面先聲明一個自定義注解(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;
}

自定義注解規則:

  1. 注解方法不帶參數,比如name()website()
  2. 注解方法返回值類型:基本類型、String、Enums、Annotation以及前面這些類型的數組類型
  3. 注解方法可有默認值,比如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,故可在運行期通過反射機制來使用,否則無法通過反射機制來獲取。

4.2 注解解析

接下來,通過反射技術來解析自定義注解@AuthorAnno,關於反射類位於包java.lang.reflect,其中有一個接口AnnotatedElement,該接口定義了注釋相關的幾個核心方法,如下:

返回值 方法 解釋 T getAnnotation(Class annotationClass) 當存在該元素的指定類型注解,則返回相應注釋,否則返回null Annotation[] getAnnotations() 返回此元素上存在的所有注解 Annotation[] getDeclaredAnnotations() 返回直接存在於此元素上的所有注解。 boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 當存在該元素的指定類型注解,則返回true,否則返回false

前面自定義的注解,適用對象為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,完全可以根據拿到的注解信息做更多有意義的事。

 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved