Java反射API研究(1)——注解Annotation


  注解在表面上的意思,只是標記一下這一部分,最好的注解就是代碼自身。而在java上,由於注解的特殊性,可以通過反射API獲取,這種特性使得注解被廣泛應用於各大框架,用於配置內容,代替xml文件配置。

  要學會注解的使用,最簡單的就是定義自己的注解,所以需要先了解一個java的元注解

1、元注解--注解的注解

  元注解的作用就是負責注解其他注解,在java1.6上,只有四個元注解:@Target、@Retention、@Documented、@Inherited。在java1.8上,多了@Native與@Repeatable。下面先說說這幾個元注解

  (1)、Documented

    這個純粹是語義元注解,指示某一類型的注解將通過 javadoc 和類似的默認工具進行文檔化。應使用此類型來注解這些類型的聲明:其注解會影響由其客戶端注解的元素的使用。如果類型聲明是用 Documented 來注解的,則其注解將成為注解元素的公共 API 的一部分。被這個注解注解的注解(真拗口...)會在自動生成api文檔時加載文檔中。他的聲明時這樣的:

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

  (2)、Inherited

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

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

    即一個類中,沒有@Father的注解,但是這個類的父類有@Father注解,且@Father注解被@Inherited注解,則在使用反射獲取子類@Father注解時,是可以獲取到父類的@Father注解的。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。

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

  (3)、Retention

    指示注解類型的注解要保留多久。如果注解類型聲明中不存在 Retention 注解,則保留策略默認為 RetentionPolicy.CLASS。只有元注解類型直接用於注解時,Target 元注解才有效。如果元注解類型用作另一種注解類型的成員,則無效。 

    某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因為Annotation與class在使用上是被分離的)。使用這個meta-Annotation可以對 Annotation的“生命周期”限制。

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

    value值為類型為RetentionPolicy,是本包的一個枚舉類型,包含三個值:

    RetentionPolicy.SOURCE  只在源代碼中出現,編譯器要丟棄的注解。

    RetentionPolicy.CLASS  編譯器將把注解記錄在類文件中,但在運行時 VM 不需要保留注解。

    RetentionPolicy.RUNTIME  編譯器將把注解記錄在類文件中,在運行時 VM 將保留注解,因此可以反射性地讀取。

    PS:當注解中只有一個屬性(或只有一個屬性沒有默認值),且該屬性為value,則可在使用注解時直接括號中對value賦值,而不用顯式指定value = RetentionPolicy.CLASS

 

  (4)、Target

    指示注解類型所適用的程序元素的種類。如果注解類型聲明中不存在 Target 元注解,則聲明的類型可以用在任一程序元素上。如果存在這樣的元注解,則編譯器強制實施指定的使用限制。 例如,此元注解指示該聲明類型是其自身,即元注解類型。它只能用在注解類型聲明上:

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

    要想聲明只能用於某個注解的成員類型使用的注解,則:

    @Target({}) 

    ElementType 常量在 Target 注解中至多只能出現一次,如下是非法的:

    @Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})

    value數組類型為ElementType,同樣是本包的一個枚舉類型,他包含的值較多,參考如下:ElementType.

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

  (5)、Repeatable  可重復注解的注解

    允許在同一申明類型(類,屬性,或方法)的多次使用同一個注解。

    在這個注解出現前,一個位置要想注兩個相同的注解,是不可能的,編譯會出錯誤。所以要想使一個注解可以被注入兩次,需要聲明一個高級注解,這個注解中的成員類型為需要多次注入的注解的注解數組,如:

public @interface Authority {
     String role();
}
 
public @interface Authorities {
    Authority[] value();
}
 
public class RepeatAnnotationUseOldVersion {
    @Authorities({@Authority(role="Admin"),@Authority(role="Manager")})
    public void doSomeThing(){
    }
}

    由另一個注解來存儲重復注解,在使用時候,用存儲注解Authorities來擴展重復注解。這樣可以實現為一個方法注解兩個Authority,但是這樣可讀性比較差。

    通過Repeatable可以這樣實現上面的效果:

@Repeatable(Authorities.class)
public @interface Authority {
     String role();
}
 
public @interface Authorities {
    Authority[] value();
}
 
public class RepeatAnnotationUseNewVersion {
    @Authority(role="Admin")
    @Authority(role="Manager")
    public void doSomeThing(){ }
}

  在注解Authority上告訴該注解,如果多次用Authority注解了某個方法,則自動把多次注解Authority作為Authorities注解的成員數組的一個值,當取注解時,可以直接取Authorities,即可取到兩個Authority注解。要求:@Repeatable注解的值的注解類Authorities.class,成員變量一定是被注解的注解Authority的數組。

  不同的地方是,創建重復注解Authority時,加上@Repeatable,指向存儲注解Authorities,在使用時候,直接可以重復使用Authority注解。從上面例子看出,java 8里面做法更適合常規的思維,可讀性強一點

  其實和第一種是一模一樣的,只是增加了可讀性。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

  (6)、Native  

    Indicates that a field defining a constant value may be referenced from native code. The annotation may be used as a hint by tools that generate native header files to determine whether a header file is required, and if so, what declarations it should contain.

    僅僅用來標記native的屬性

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}

     只對屬性有效,且只在代碼中使用,一般用於給IDE工具做提示用。

 2、編寫自己的注解:注解接口  Annotation

  所有注解默認都實現了這個接口,實現是由編譯器完成的,編寫自己的接口的方法:

  使用@interface自定義注解時,自動繼承了java.lang.annotation.Annotation接口,由編譯程序自動完成其他細節。在定義注解時,不能繼承其他的注解或接口。@interface用來聲明一個注解,其中的每一個方法實際上是聲明了一個配置參數。方法的名稱就是參數的名稱,返回值類型就是參數的類型(返回值類型只能是基本類型、Class、String、enum)。可以通過default來聲明參數的默認值。同時value屬性是一個注解的默認屬性,只有value屬性時是可以不顯示賦值的。

  定義注解格式:

  public @interface 注解名 {定義體}

  使用注解格式:

  @注解名(key=value, key=value)

  注解參數的可支持數據類型: 

  1.所有基本數據類型(int,float,boolean,byte,double,char,long,short)
  2.String類型
  3.Class類型
  4.enum類型
  5.Annotation類型
  6.以上所有類型的數組

  Annotation類型里面的參數該怎么設定: 
  第一,只能用public或默認(default)這兩個訪問權修飾.例如,String value();這里把方法設為defaul默認類型;   
  第二,參數成員只能用基本類型byte,short,char,int,long,float,double,boolean八種基本數據類型和 String,Enum,Class,Annotation等數據類型,以及這一些類型的數組.例如,String value();這里的參數成員就為String;數組類型類似於String[] value();
  第三,如果只有一個參數成員,最好把參數名稱設為"value",后加小括號.或者只有一個參數沒有默認值,其他都有,也可以把這個參數名稱設為"value",這樣使用注解時就不用顯式聲明屬性了。

  第四,如果一個參數成員類型為數組,如果 String[] array();傳值方式為array={"a","b"},若只有一個值,則可以直接令array="a",會自動生成一個只包含a的數組。若沒有值,則array={}。都是可以的。 

  注解元素的默認值:
    注解元素必須有確定的值,要么在定義注解的默認值中指定,要么在使用注解時指定,非基本類型的注解元素的值不可為null。因此, 使用空字符串或0作為默認值是一種常用的做法。這個約束使得處理器很難表現一個元素的存在或缺失的狀態,因為每個注解的聲明中,所有元素都存在,並且都具有相應的值,為了繞開這個約束,我們只能定義一些特殊的值,例如空字符串或者負數,一次表示某個元素不存在,在定義注解時,這已經成為一個習慣用法。

  Annotation接口中方法:

  Class<? extends Annotation> annotationType()   返回此 annotation 的注解類型。

  boolean equals(Object obj)    如果指定的對象表示在邏輯上等效於此接口的注解,則返回 true。

  String toString()  返回此 annotation 的字符串表示形式。

  所有Annotation類中的Class<?> getClass()。

3、通過反射獲取Annotation類對象

  注解對象是在一個類的class對象中的,一個類只有一個class實例,所以Annotation也是唯一的,對應於一個class文件。注:一個Class對象實際上表示的是一個類型,而這個類型未必一定是一種類。例如,int不是類,但int.class是一個Class類型的對象。虛擬機為每個類型管理一個Class對象。因此,可以用==運算符實現兩個類對象比較的操作。

  如果沒有用來讀取注解的方法和工作,那么注解也就不會比注釋更有用處了。使用注解的過程中,很重要的一部分就是創建於使用注解處理器。Java SE5擴展了反射機制的API,以幫助程序員快速的構造自定義注解處理器。  

  反射中獲取注解的類庫,注解處理器類庫(java.lang.reflect.AnnotatedElement):

  Java使用Annotation接口來代表程序元素前面的注解,該接口是所有Annotation類型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素,該接口主要有如下幾個實現類:

說明 對應的ElementType
Class 類定義 TYPE、ANNOTATION_TYPE
Constructor 構造器定義 CONSTRUCTOR
Field 類的成員變量定義 FIELD
Method 類的方法定義 METHOD
Package 類的包定義 PACKAGE

  注1:TYPE其實已經包含了ANNOTATION_TYPE,這個只是為了更細分

  注2:上面沒有提到的ElementType.PARAMETER,可以使用Method類的Annotation[][] getParameterAnnotations() 方法獲取,多個參數每個參數都可能有多個注解,所以才是二維數組。

  注3:LOCAL_VARIABLE暫時不知道怎么獲取,好像也沒啥必要獲取。

   方法使用:AnnotatedElement接口中有四個方法,用於獲取注解類型

  <T extends Annotation> T getAnnotation(Class<T> annotationClass)   如果存在該元素的指定類型的注釋,則返回這些注釋,否則返回 null。

  Annotation[] getAnnotations()   返回此元素上存在的所有注釋。

  Annotation[] getDeclaredAnnotations()   返回直接存在於此元素上的所有注釋。

  boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)  如果指定類型的注釋存在於此元素上,則返回 true,否則返回 false。

  用法:注解類型 anno = Class.getAnnotation(注解類型.class)

    之后就可以調用注解類型中的屬性來獲取屬性值了。示例:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Father {
    String value();
}

@Father("bca")
public class Son {
    public static void main(String[] args) {
        Father father = Son.class.getAnnotation(Father.class);
        System.out.println(father.value());
    }
}

   Java8中又補充了三個方法,用於對@Repeatable進行支持:

  default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)  返回直接存在於此元素上的指定類型的注釋。忽略繼承的注解。

  default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)  返回重復注解的類型,被同注解注解的元素返回該類型注解的數組。

  default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> anotationClass)  返回重復注解的類型,被同注解注解的元素返回注解的數組。忽略繼承的注解。

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
public class RepeatingAnnotations {
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface Filters {
        Filter[] value();
    }
     
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable( Filters.class )
    public @interface Filter {
        String value();
    };
     
    @Filter( "filter1" )
    @Filter( "filter2" )
    public interface Filterable {        
    }
     
    public static void main(String[] args) {
        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
            System.out.println( filter.value() );
        }
    }
}

 

  注意:Annotation是一個特殊的class,類似於enum,由於與普通class的特異性,使用getAnnocation獲取的返回值,其實Annotation的代理類:sun.reflect.annotation.AnnotationInvocationHandle,所有對注解內屬性的訪問都是通過代理類實現的。關於代理請看后面文章。

  http://www.2cto.com/kf/201502/376988.html


免責聲明!

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



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