注解 Annotation 簡介 總結 MD


Markdown版本筆記 我的GitHub首頁 我的博客 我的微信 我的郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目錄

注解 Annotation

在java中,注解作為程序的元數據嵌入到程序當中,元數據標簽的存在並不影響程序代碼的編譯和執行

所謂Annotation就是提供了一種為程序元素設置元數據的方法,可用於修飾包、類、構造器、方法、成員變量、參數和局部變量的聲明。注解可以被一些解析工具或者是編譯工具進行解析。Annotation中的信息可以在編譯、加載和運行時被讀取,並執行相應的處理。

當前許多Java框架中大量使用注解,如Hibernate、Jersey、Spring。

背景知識

什么是元數據 Metadata

元數據(Metadata),又稱中介數據、中繼數據,為描述數據的數據(data about data),主要是描述數據屬性(property)的信息,用來支持如指示存儲位置、歷史數據、資源查找、文件記錄等功能。

元數據是指從信息資源中抽取出來的用於說明其特征、內容的結構化的數據(如題名、版本、出版數據、相關說明、檢索點等),用於組織、描述、檢索、保存、管理信息和知識資源。

任何文件系統中的數據都分為數據和元數據。數據是指普通文件中的實際數據,而元數據指用來描述一個文件的特征的系統數據,諸如訪問權限、文件擁有者以及文件數據塊的分布信息(inode…)等等。在集群文件系統中,分布信息包括文件在磁盤上的位置以及磁盤在集群中的位置。用戶需要操作一個文件必須首先得到它的元數據,才能定位到文件的位置並且得到文件的內容或相關屬性。

HTML的head里有一個meta標簽。根據上面的解釋,我們應該知道它是“關於文檔的信息”了。meta的屬性有兩種,name和http-equiv,name屬性用來描述網頁的內容,http-equiv屬性指示服務器在發送實際的文檔之前先在要傳送給瀏覽器的 MIME 文檔頭部包含名稱/值對,比如用以說明主頁制作所使用的文字以及語言。

以下為百度和新浪首頁的meta標簽:

//百度首頁meta標簽
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="referrer" content="always">
<meta name="theme-color" content="#2932e1">
//新浪首頁部分meta標簽
<meta name="keywords" content="新浪,新浪網,SINA,sina,sina.com.cn,新浪首頁,門戶,資訊" />
<meta name="description" content="新浪網為全球用戶24小時提供全面及時的中文資訊,***。" />
<meta name="application-name" content="新浪首頁"/>

基於元數據的廣泛應用,JDK5.0引入了Annotation的概念來描述元數據。

為什么要引入 Annotation

使用Annotation之前,XML被廣泛的應用於描述元數據。不知何時開始一些應用開發人員和架構師發現XML的維護越來越糟糕了。他們希望使用一些和代碼緊耦合的東西,而不是像XML那樣和代碼是松耦合的(在某些情況下甚至是完全分離的)代碼描述。如果你在Google中搜索“XML vs. annotations”,會看到許多關於這個問題的辯論。最有趣的是XML配置其實就是為了分離代碼和配置而引入的。上述兩種觀點可能會讓你很疑惑,兩者觀點似乎構成了一種循環,但各有利弊。下面我們通過一個例子來理解這兩者的區別。

假如你想為應用設置很多的常量或參數,這種情況下,XML是一個很好的選擇,因為它不會同特定的代碼相連。如果你想把某個方法聲明為服務,那么使用Annotation會更好一些,因為這種情況下需要注解和方法緊密耦合起來,開發人員也必須認識到這點。

另一個很重要的因素是Annotation定義了一種標准的描述元數據的方式。在這之前,開發人員通常使用他們自己的方式定義元數據。例如,使用標記interfaces,注解,transient關鍵字等等。每個程序員按照自己的方式定義元數據,而不像Annotation這種標准的方式。

目前,許多框架將XML和Annotation兩種方式結合使用,平衡兩者之間的利弊。

XML 和 Annotation 的優缺點

目前 web 應用中幾乎都使用 xml 作為配置項,xml 之所以這么流行,是因為它的很多優點是其它技術的配置所無法替代的。
xml的優點:

  • xml 作為可擴展標記語言最大的優勢在於開發者能夠為軟件量身【定制】適用的標記,使代碼更加通俗易懂。
  • 利用 xml 配置能使軟件更具擴展性。例如 Spring 將 class 間的依賴配置在 xml 中,最大限度地提升應用的可擴展性。
  • 具有成熟的驗證機制確保程序正確性。利用 Schema 或 DTD 可以對 xml 的正確性進行驗證,避免了非法的配置導致應用程序出錯。
  • 修改配置而無需變動現有程序。

雖然有如此多的好處,但畢竟沒有什么萬能的東西,xml 也有自身的缺點。xml的缺點:

  • 需要解析工具或類庫的支持。
  • 解析 xml 勢必會影響應用程序性能,占用系統資源。
  • 配置文件過多導致管理變得困難。
  • 編譯期無法對其配置項的正確性進行驗證,或要查錯只能在運行期。
  • IDE 無法驗證配置項的正確性。
  • 查錯變得困難,往往配置的一個手誤導致莫名其妙的錯誤。
  • 開發人員不得不同時維護代碼和配置文件,開發效率變得低下。
  • 配置項與代碼間存在潛規則。改變了任何一方都有可能影響另外一方。

Annotation 的優點:

  • 保存在 class 文件中,降低維護成本。
  • 無需工具支持,無需解析。
  • 編譯期即可驗證正確性,查錯變得容易。
  • 提升開發效率。

Annotation的缺點:

  • 若要對配置項進行修改,不得不修改 Java 文件,重新編譯打包應用。
  • 配置項編碼在 Java 文件中,可擴展性差。

沒有一個事物是萬能的,同樣 xml 和 java Annotation 都有各自的優缺點。且他們的優缺點恰恰是互補的,xml 的強項是 Annotation 所不具備的,而 Annotation 的優勢也是 xml 所欠缺的。這也正是時下流行的 xml + Annotation 配置的原因所在。

注解的定義

定義一個Annotation類型使用@interface關鍵字,定義一個Annotation類型與定義一個接口非常像(只是多了一個@符號)。

public @interface TestAnnotation {
}

Annotation可以是上面的簡單形式,還可以包含成員變量。成員變量的一些規則:

  • Annotation的成員變量以無形參的方法形式來聲明,其方法名和返回值定義了該成員變量的名字和類型
  • Annotation的屬性類型只能是基本類型、String、enum、Class及上述類型的一維數組類型
  • 使用帶有屬性的Annotation時,必須為其所有定義的屬性指定值(使用default的可以不用指定)
  • 定義Annotation時可以使用default關鍵字為屬性設置默認值,使用時不為該屬性指定值時會使用默認值
  • 如果Annotation中具有名為value的屬性,在使用時如果只使用value屬性的話,可以不寫屬性名直接指定值
  • 對於數組類型,當數組中只有一個元素時,可以省略大括號

注解的定義案例

UserInfo

public @interface UserInfo {
    String username();
    String data() default "2017年9月2日";
    int age();
    SexEnum sex();

    public static enum SexEnum {
        男, 女, 其他
    }
}

Citys

public @interface Citys {
    String username() default "包青天";
    String[] value();
}

使用

@UserInfo(age = 28, sex = UserInfo.SexEnum.男, username = "白乾濤")
public class Test {

    @Citys({ "廣州", "深圳", "長沙", "呵呵", })  //后面可以帶一個逗號,以方便擴展
    @UserInfo(age = 28, sex = UserInfo.SexEnum.男, username = "白乾濤", data = "2017.9.2")
    public static void main(String[] args) {
        @Citys(value = { "廣州", "深圳", "長沙", "呵呵" }, username = "白乾濤")
        int i = 1;
    }

    @Citys("廣州")
    String string = "只有一個元素時,可以省略大括號";
}

四個元注解

元注解的作用就是負責注解其他注解

Java5.0定義了4個標准的 meta-annotation 類型,它們被用來提供對其它 annotation 類型作說明(只能作用在注解上,不能作用在其他程序元素上)。

Java5.0定義的四個元注解為:@Target@Retention@Documented@Inherited
這些類型和它們所支持的類在 java.lang.annotation 包中可以找到。

Target 適用的元素種類

Target 的完整定義

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface java.lang.annotation.Target {
    /**
     * @return an array of the kinds of elements an annotation type can be applied to
     */
    ElementType[] value();
}

指示注解類型所適用的程序元素的種類

@Target用來限定一個Annotation可以用於修飾哪些程序元素(可修飾的程序元素由 ElementType 限定),例如方法、成員變量等。

如果注解類型聲明中不存在 Target 元注解,則聲明的類型可以用在任一程序元素上;如果存在這樣的元注解,則編譯器強制實施指定的使用限制。

如上例中的 UserInfo,其可以作用到類(類是TYPE的一種)和方法(METHOD)上,所以用@Target來限定的話就是:

@Target(value = { ElementType.TYPE, ElementType.METHOD })
public @interface UserInfo { ... }

同樣,用元注解@Target對注解Citys來限定的話就是:

@Target(value = { ElementType.METHOD, ElementType.LOCAL_VARIABLE, ElementType.FIELD })
public @interface Citys { ... }

如果一個注解使用元注解@Target(ElementType.ANNOTATION_TYPE)來限定,則表明此注解只能用在注解類型元素的聲明上,那么此注解就是一個元注解。實際上,四個元注解就是這么定義的:

@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {...}

Retention 保留策略

Retention 的完整定義

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface java.lang.annotation.Retention {
    /**
     * @return the retention policy
     */
    RetentionPolicy value();
}

指示注解類型的注解要保留多久。

如果注解類型聲明中不存在 Retention 注解,則保留策略默認為 RetentionPolicy.CLASS

注意,使用默認保留策略的注解,不能通過反射獲取注解信息。

只有元注解類型直接用於注解時,Target 元注解才有效。如果元注解類型用作另一種注解類型的成員,則無效。

四個元注解的定義都是@Retention(RetentionPolicy.RUNTIME),也即編譯器將把注解記錄在類文件中,在運行時 VM 將保留注解,因此可以通過反射讀取。

Documented 文檔化

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

Indicates指示、表明、標志 that annotations with a type are to be documented by javadoc and similar tools by default. This type should be used to annotate注釋、注解、批注、注 the declarations聲明、宣言、發表 of types whose annotations affect the use of annotated elements by their clients. If a type declaration is annotated with Documented, its annotations become part of the public API of the annotated elements.

指示某一類型的注釋將通過 javadoc 和類似的默認工具進行文檔化。應使用此類型來注解這些類型的聲明:其注解會影響由其客戶端注解的元素的使用。如果一個類型的聲明是用 Documented 來注解的,則其注解將成為注解元素的公共 API 的一部分。

效果測試

//@Documented
public @interface TestDocumented {
    String value();
}

@TestDocumented("白乾濤")
public class Test {
    @TestDocumented("白乾濤")
    public static void main(String[] args) {
    }
}

如果不加@Documented,默認情況下,javadoc是不包括注解的,此時生成的文檔如下:

如果聲明注解時指定了 @Documented,則它就會被 javadoc 之類的工具處理,所以注解類型信息也會被包括在生成的文檔中,此時生成的文檔如下:

其實沒有什么卵用

Inherited 自動繼承

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

Indicates指示、表明、標志 that an annotation type is automatically inherited. If an Inherited meta-annotation is present存在 on an annotation type declaration, and the user queries查詢 the annotation type on a class declaration, and the class declaration has no annotation for this type, then the class's superclass will automatically be queried for the annotation type. This process will be repeated until an annotation for this type is found, or the top of the class hierarchy (Object) is reached. If no superclass has an annotation for this type, then the query will indicate that the class in question has no such annotation.

指示注解類型被自動繼承。如果在注解類型聲明中存在 Inherited 元注解,並且用戶在某一類聲明中查詢該注解類型,同時該類聲明中沒有此類型的注解,則將在該類的超類中自動查詢該注解類型。此過程會重復進行,直到找到此類型的注解或到達了該類層次結構的頂層 (Object) 為止。如果沒有超類具有該類型的注解,則查詢將指示當前類沒有這樣的注解。

Note that this meta-annotation type has no effect if the annotated type is used to annotate anything other than a class. Note also that this meta-annotation only causes annotations to be inherited from superclasses; annotations on implemented interfaces have no effect.

注意,如果使用注解類型注解類以外的任何事物,此元注解類型都是無效的。還要注意,此元注解僅促成從超類繼承注解;對已實現接口的注解無效。

這是一個稍微復雜的注解類型,它指明被注解的類會自動繼承。更具體地說,如果定義注解時使用了@Inherited 標記,然后用定義的注解來標注另一個父類,父類又有一個子類,則父類的所有屬性將被繼承到它的子類中。

基本沒用到過

三個基本的注解

在 java.lang 包中提供了3個基本Annotation的用法,可以通過查看API文檔來了解。

Override 重寫

@Target(ElementType.METHOD)//可以看出,@Override只能修飾方法,不能修飾其他程序元素
@Retention(RetentionPolicy.SOURCE)
public @interface java.lang.Override {
}

表示一個方法聲明打算重寫超類中的另一個方法聲明。如果方法利用此注釋類型進行注解,但沒有重寫超類方法,則編譯器會生成一條錯誤消息。

Deprecated 廢棄

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

A program element annotated @Deprecated is one that programmers are discouraged from using, typically because it is dangerous, or because a better alternative exists. Compilers warn when a deprecated program element is used or overridden in non-deprecated code.

使用@Deprecated注釋的程序元素,是不鼓勵程序員使用的元素,通常是因為它很危險或存在更好的選擇。 在使用不被贊成的程序元素,或在不被贊成的代碼中執行重寫時,編譯器會發出警告。

SuppressWarnings 不警告

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface java.lang.SuppressWarnings {
    /**
     * @return the set of warnings to be suppressed
     */
    String[] value();
}

Indicates that the named compiler warnings指定的編譯器警告 should be suppressed壓制、取消 in the annotated element (and in all program elements contained in the annotated element).

指示應該在注釋元素(以及包含在該注釋元素中的所有程序元素)中,取消顯示指定的編譯器警告。

Note that the set of warnings suppressed in a given element is a superset of the warnings suppressed in all containing elements. For example, if you annotate a class to suppress one warning and annotate a method to suppress another, both warnings will be suppressed in the method.

注意,在給定元素中取消顯示的警告集,是所有包含元素中取消顯示的警告的超集。例如,如果注釋一個類來取消顯示某個警告,同時注釋一個方法來取消顯示另一個警告,那么將在此方法中同時取消顯示這兩個警告。

As a matter of style, programmers should always use this annotation on the most deeply nested element where it is effective有效. If you want to suppress a warning in a particular特定的 method, you should annotate that method rather than its class.

根據風格不同,程序員應該始終在最里層的嵌套元素上使用此注釋,在那里使用才有效。例如,如果要在特定的方法中取消顯示某個警告,則應該注釋該方法而不是注釋它的類。

兩個相關的枚舉類

ElementType 元素類型

public enum java.lang.annotation.ElementType extends Enum<ElementType>

程序元素類型。此枚舉類型的常量提供了 Java 程序中聲明的元素的簡單分類。
這些常量與 Target 元注解類型一起使用,以指定在什么情況下使用注解類型是合法的。

枚舉常量

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

方法

static ElementType    valueOf(String name)

返回帶有指定名稱的該類型的枚舉常量。
字符串必須與用於聲明該類型的枚舉常量的 標識符 完全匹配(不允許有多余的空格,大小寫敏感)。
如果該枚舉類型沒有帶有指定名稱的常量, - 則拋出 IllegalArgumentException

static ElementType[]    values()

Returns an array containing the constants of this enum type, in the order they are declared.

案例:

System.out.println(ElementType.values().length);//10
for (ElementType c : ElementType.values()) {
    System.out.print(c + "  ");//TYPE  FIELD  METHOD  PARAMETER  CONSTRUCTOR
    //LOCAL_VARIABLE  ANNOTATION_TYPE  PACKAGE  TYPE_PARAMETER  TYPE_USE  
}
System.out.println(ElementType.valueOf("FIELD"));//FIELD

RetentionPolicy 保留策略

public enum java.lang.annotation.RetentionPolicy extends Enum<RetentionPolicy>

注解保留策略。此枚舉類型的常量描述保留注解的不同策略。它們與 Retention 元注解類型一起使用,以指定保留多長的注解。

枚舉常量

  • CLASS 在class文件中有效。編譯器將把注解記錄在類文件中,但在運行時 VM 不需要保留注解。這是默認的行為。也就是說,默認行為是:當運行Java程序時,JVM不可獲取Annotation信息。
  • RUNTIME 在運行時有效。編譯器將把注解記錄在類文件中,在運行時 VM 將保留注解,因此可以反射性地讀取。
  • SOURCE 只在源文件中有效。編譯器要丟棄的注解。

方法

static RetentionPolicy    valueOf(String name)

返回帶有指定名稱的該類型的枚舉常量。
字符串必須與用於聲明該類型的枚舉常量的 標識符 完全匹配(不允許有多余的空格,大小寫敏感)。
如果該枚舉類型沒有帶有指定名稱的常量, - 則拋出 IllegalArgumentException

static RetentionPolicy[]    values()

Returns an array containing the constants of this enum type, in the order they are declared.

案例:

System.out.println(RetentionPolicy.values().length);//3
for (RetentionPolicy r : RetentionPolicy.values()) {
    System.out.print(r + "  ");//SOURCE  CLASS  RUNTIME  
}
System.out.println(RetentionPolicy.valueOf("CLASS"));//CLASS

一個接口 Annotation

public interface java.lang.annotation.Annotation 

The common interface extended by all annotation types. Note that an interface that manually extends this one does not define an annotation type. Also note that this interface does not itself define an annotation type.

所有 annotation 類型都要擴展的公共接口。注意,手動擴展該公共接口的接口不定義 annotation 類型。還要注意此接口本身不定義 annotation 類型。

More information about annotation types can be found in section 9.6 of The Java™ Language Specification. The java.lang.reflect.AnnotatedElement interface discusses compatibility兼容性 concerns when evolving an annotation type from being non-repeatable to being repeatable.

有關注解類型的更多信息,請參見Java™語言規范第9.6節。 java.lang.reflect.AnnotatedElement接口討論了,將注解類型從不可重復轉變為可重復時的,兼容性問題。

方法

  • boolean equals(Object obj)
  • int hashCode()
  • String toString() 返回此 annotation 的字符串表示形式。表示形式的細節取決於實現,但下面的情況是最常見的@com.bqt.Citys(username=包青天, value=[廣州, 深圳])
  • Class<? extends Annotation> annotationType() 返回此 annotation 的注解類型。

案例:

@Citys({ "廣州", "深圳", })//必須設置保留策略為 RUNTIME 才能通過反射獲取注解信息
Citys ann = method.getAnnotation(Citys.class);
System.out.println(ann.username() + "  " + Arrays.toString(ann.value()) + "  " + ann.annotationType());//包青天  [廣州, 深圳]  interface com.bqt.Citys
System.out.println(ann.toString());//@com.bqt.Citys(username=包青天, value=[廣州, 深圳])

2017-9-3


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM