Java自定義註解和運行時靠反射獲取註解

java自定義註解

Java註解是附加在代碼中的一些元信息,用於一些工具在編譯、運行時進行解析和使用,起到說明、配置的功能。
註解不會也不能影響代碼的實際邏輯,僅僅起到輔助性的作用。包含在 java.lang.annotation 包中。

1、元註解

元註解是指註解的註解。包括  @Retention @Target @Document @Inherited四種。


1.1、@Retention: 定義註解的保留策略

@Retention(RetentionPolicy.SOURCE)   //註解僅存在於源碼中,在class字節碼文件中不包含

@Retention(RetentionPolicy.CLASS)     // 默認的保留策略,註解會在class字節碼文件中存在,但運行時無法獲得,

@Retention(RetentionPolicy.RUNTIME)  // 註解會在class字節碼文件中存在,在運行時可以通過反射獲取到


註解類:

[java] view plaincopyprint?在CODE上查看代碼片派生到我的代碼片

  1. @Retention(RetentionPolicy.RUNTIME) // 註解會在class字節碼文件中存在,在運行時可以通過反射獲取到  

  2. @Target({ElementType.FIELD,ElementType.METHOD})//定義註解的作用目標**作用範圍字段、枚舉的常量/方法  

  3. @Documented//說明該註解將被包含在javadoc中  

  4. public @interface FieldMeta {  

  5.   

  6.     /** 

  7.      * 是否爲序列號 

  8.      * @return 

  9.      */  

  10.     boolean id() default false;  

  11.     /** 

  12.      * 字段名稱 

  13.      * @return 

  14.      */  

  15.     String name() default "";  

  16.     /** 

  17.      * 是否可編輯 

  18.      * @return 

  19.      */  

  20.     boolean editable() default true;  

  21.     /** 

  22.      * 是否在列表中顯示 

  23.      * @return 

  24.      */  

  25.     boolean summary() default true;  

  26.     /** 

  27.      * 字段描述 

  28.      * @return 

  29.      */  

  30.     String description() default "";  

  31.     /** 

  32.      * 排序字段 

  33.      * @return 

  34.      */  

  35.     int order() default 0;  

  36. }  


實體類:

[java] view plaincopyprint?在CODE上查看代碼片派生到我的代碼片

  1. public class Anno {  

  2.   

  3.     @FieldMeta(id=true,name="序列號",order=1)  

  4.     private int id;  

  5.     @FieldMeta(name="姓名",order=3)  

  6.     private String name;  

  7.     @FieldMeta(name="年齡",order=2)  

  8.     private int age;  

  9.       

  10.     @FieldMeta(description="描述",order=4)  

  11.     public String desc(){  

  12.         return "java反射獲取annotation的測試";  

  13.     }  

  14.       

  15.     public int getId() {  

  16.         return id;  

  17.     }  

  18.     public void setId(int id) {  

  19.         this.id = id;  

  20.     }  

  21.     public String getName() {  

  22.         return name;  

  23.     }  

  24.     public void setName(String name) {  

  25.         this.name = name;  

  26.     }  

  27.     public int getAge() {  

  28.         return age;  

  29.     }  

  30.     public void setAge(int age) {  

  31.         this.age = age;  

  32.     }  

  33.       

  34. }  


獲取到註解的幫助類:

[java] view plaincopyprint?在CODE上查看代碼片派生到我的代碼片

  1. public class SortableField {  

  2.   

  3.     public SortableField(){}  

  4.       

  5.     public SortableField(FieldMeta meta, Field field) {  

  6.         super();  

  7.         this.meta = meta;  

  8.         this.field = field;  

  9.         this.name=field.getName();  

  10.         this.type=field.getType();  

  11.     }  

  12.       

  13.       

  14.     public SortableField(FieldMeta meta, String name, Class<?> type) {  

  15.         super();  

  16.         this.meta = meta;  

  17.         this.name = name;  

  18.         this.type = type;  

  19.     }  

  20.   

  21.   

  22.     private FieldMeta meta;  

  23.     private Field field;  

  24.     private String name;  

  25.     private Class<?> type;  

  26.       

  27.     public FieldMeta getMeta() {  

  28.         return meta;  

  29.     }  

  30.     public void setMeta(FieldMeta meta) {  

  31.         this.meta = meta;  

  32.     }  

  33.     public Field getField() {  

  34.         return field;  

  35.     }  

  36.     public void setField(Field field) {  

  37.         this.field = field;  

  38.     }  

  39.     public String getName() {  

  40.         return name;  

  41.     }  

  42.     public void setName(String name) {  

  43.         this.name = name;  

  44.     }  

  45.   

  46.     public Class<?> getType() {  

  47.         return type;  

  48.     }  

  49.   

  50.     public void setType(Class<?> type) {  

  51.         this.type = type;  

  52.     }  

  53.       

  54.       

  55. }  


運行時獲取註解,首先創建一個基類:

[java] view plaincopyprint?在CODE上查看代碼片派生到我的代碼片

  1. public class Parent<T> {  

  2.   

  3.     private Class<T> entity;  

  4.   

  5.     public Parent() {  

  6.         init();  

  7.     }  

  8.   

  9.     @SuppressWarnings("unchecked")  

  10.     public List<SortableField> init(){  

  11.         List<SortableField> list = new ArrayList<SortableField>();  

  12.         /**getClass().getGenericSuperclass()返回表示此 Class 所表示的實體(類、接口、基本類型或 void) 

  13.          * 的直接超類的 Type(Class<T>泛型中的類型),然後將其轉換ParameterizedType。。 

  14.          *  getActualTypeArguments()返回表示此類型實際類型參數的 Type 對象的數組。 

  15.          *  [0]就是這個數組中第一個了。。 

  16.          *  簡而言之就是獲得超類的泛型參數的實際類型。。*/  

  17.         entity = (Class<T>)((ParameterizedType)this.getClass().getGenericSuperclass())  

  18.                 .getActualTypeArguments()[0];  

  19. //      FieldMeta filed = entity.getAnnotation(FieldMeta.class);  

  20.           

  21.         if(this.entity!=null){  

  22.               

  23.             /**返回類中所有字段,包括公共、保護、默認(包)訪問和私有字段,但不包括繼承的字段 

  24.              * entity.getFields();只返回對象所表示的類或接口的所有可訪問公共字段 

  25.              * 在class中getDeclared**()方法返回的都是所有訪問權限的字段、方法等; 

  26.              * 可看API 

  27.              * */  

  28.             Field[] fields = entity.getDeclaredFields();  

  29. //            

  30.             for(Field f : fields){  

  31.                 //獲取字段中包含fieldMeta的註解  

  32.                 FieldMeta meta = f.getAnnotation(FieldMeta.class);  

  33.                 if(meta!=null){  

  34.                     SortableField sf = new SortableField(meta, f);  

  35.                     list.add(sf);  

  36.                 }  

  37.             }  

  38.               

  39.             //返回對象所表示的類或接口的所有可訪問公共方法  

  40.             Method[] methods = entity.getMethods();  

  41.               

  42.             for(Method m:methods){  

  43.                 FieldMeta meta = m.getAnnotation(FieldMeta.class);  

  44.                 if(meta!=null){  

  45.                     SortableField sf = new SortableField(meta,m.getName(),m.getReturnType());  

  46.                     list.add(sf);  

  47.                 }  

  48.             }  

  49.             //這種方法是新建FieldSortCom類實現Comparator接口,來重寫compare方法實現排序  

  50. //          Collections.sort(list, new FieldSortCom());  

  51.             Collections.sort(list, new Comparator<SortableField>() {  

  52.                 @Override  

  53.                 public int compare(SortableField s1,SortableField s2) {  

  54.                     return s1.getMeta().order()-s2.getMeta().order();  

  55. //                  return s1.getName().compareTo(s2.getName());//也可以用compare來比較  

  56.                 }  

  57.                   

  58.             });  

  59.         }  

  60.         return list;  

  61.           

  62.     }  

  63. }  

創建子類繼承基類:

[java] view plaincopyprint?在CODE上查看代碼片派生到我的代碼片

  1. public class Child extends Parent<Anno>{  

  2.   

  3. }  


測試類:

[java] view plaincopyprint?在CODE上查看代碼片派生到我的代碼片

  1. public class TestAnnotation {  

  2.   

  3.     @SuppressWarnings({ "unchecked""rawtypes" })  

  4.     public static void main(String[] args) {  

  5.         Parent c = new Child();  

  6.         List<SortableField> list = c.init();//獲取泛型中類裏面的註解  

  7.         //輸出結果  

  8.         for(SortableField l : list){  

  9.             System.out.println("字段名稱:"+l.getName()+"\t字段類型:"+l.getType()+  

  10.                     "\t註解名稱:"+l.getMeta().name()+"\t註解描述:"+l.getMeta().description());  

  11.         }  

  12.     }  

  13. }  


轉:

1、Annotation的工作原理:

JDK5.0中提供了註解的功能,允許開發者定義和使用自己的註解類型。該功能由一個定義註解類型的語法和描述一個註解聲明的語法,讀取註解的API,一個使用註解修飾的class文件和一個註解處理工具組成。

Annotation並不直接影響代碼的語義,但是他可以被看做是程序的工具或者類庫。它會反過來對正在運行的程序語義有所影響。

Annotation可以衝源文件、class文件或者在運行時通過反射機制多種方式被讀取。

2、@Override註解:

java.lang
註釋類型 Override
@Target(value=METHOD)
@Retention(value=SOURCE)
public @interface Override

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

@Override註解表示子類要重寫父類的對應方法。

Override是一個Marker annotation,用於標識的Annotation,Annotation名稱本身表示了要給工具程序的信息。

下面是一個使用@Override註解的例子:

class A {    private String id;
    A(String id){        this.id = id;
    }    @Override
    public String toString() {        return id;
    }
}

3、@Deprecated註解:

java.lang
註釋類型 Deprecated
@Documented
@Retention(value=RUNTIME)
public @interface Deprecated

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

@Deprecated註解表示方法是不被建議使用的。

Deprecated是一個Marker annotation。

下面是一個使用@Deprecated註解的例子:

class A {    private String id;
    A(String id){        this.id = id;
    }    @Deprecated
    public void execute(){
        System.out.println(id);
    }    public static void main(String[] args) {
        A a = new A("a123");
        a.execute();
    }
}

4、@SuppressWarnings註解:

java.lang
註釋類型 SuppressWarnings
@Target(value={TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})
@Retention(value=SOURCE)
public @interface SuppressWarnings

指示應該在註釋元素(以及包含在該註釋元素中的所有程序元素)中取消顯示指定的編譯器警告。注意,在給定元素中取消顯示的警告集是所有包含元素中取消顯示的警告的超集。例如,如果註釋一個類來取消顯示某個警告,同時註釋一個方法來取消顯示另一個警告,那麼將在此方法中同時取消顯示這兩個警告。

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

@SuppressWarnings註解表示抑制警告。

下面是一個使用@SuppressWarnings註解的例子:

@SuppressWarnings("unchecked")public static void main(String[] args) {
    List list = new ArrayList();
    list.add("abc");
}

5、自定義註解:

使用@interface自定義註解時,自動繼承了java.lang.annotation.Annotation接口,由編譯程序自動完成其他細節。在定義註解時,不能繼承其他的註解或接口。

自定義最簡單的註解:

public @interface MyAnnotation {

}

使用自定義註解:

public class AnnotationTest2 {    @MyAnnotation
    public void execute(){
        System.out.println("method");
    }
}

5.1、添加變量:

public @interface MyAnnotation {

    String value1();
}

使用自定義註解:

public class AnnotationTest2 {    @MyAnnotation(value1="abc")
    public void execute(){
        System.out.println("method");
    }
}

當註解中使用的屬性名爲value時,對其賦值時可以不指定屬性的名稱而直接寫上屬性值接口;除了value意外的變量名都需要使用name=value的方式賦值。

5.2、添加默認值:

public @interface MyAnnotation {

    String value1() default "abc";
}

5.3、多變量使用枚舉:

public @interface MyAnnotation {

    String value1() default "abc";
    MyEnum value2() default MyEnum.Sunny;
}
enum MyEnum{
    Sunny,Rainy
}

使用自定義註解:

public class AnnotationTest2 {    @MyAnnotation(value1="a", value2=MyEnum.Sunny)
    public void execute(){
        System.out.println("method");
    }
}

5.4、數組變量:

public @interface MyAnnotation {

    String[] value1() default "abc";
}

使用自定義註解:

public class AnnotationTest2 {    @MyAnnotation(value1={"a","b"})
    public void execute(){
        System.out.println("method");
    }
}

6、設置註解的作用範圍:

@Documented
@Retention(value=RUNTIME)
@Target(value=ANNOTATION_TYPE)
public @interface Retention

指示註釋類型的註釋要保留多久。如果註釋類型聲明中不存在 Retention 註釋,則保留策略默認爲 RetentionPolicy.CLASS。

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

public enum RetentionPolicyextends Enum<RetentionPolicy>

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

CLASS編譯器將把註釋記錄在類文件中,但在運行時 VM 不需要保留註釋。RUNTIME編譯器將把註釋記錄在類文件中,在運行時 VM 將保留註釋,因此可以反射性地讀取。SOURCE編譯器要丟棄的註釋。

@Retention註解可以在定義註解時爲編譯程序提供註解的保留策略。

屬於CLASS保留策略的註解有@SuppressWarnings,該註解信息不會存儲於.class文件。

6.1、在自定義註解中的使用例子:

@Retention(RetentionPolicy.CLASS)public @interface MyAnnotation {

    String[] value1() default "abc";
}

7、使用反射讀取RUNTIME保留策略的Annotation信息的例子:

java.lang.reflect
        接口 AnnotatedElement所有已知實現類:
        AccessibleObject, Class, Constructor, Field, Method, Package

表示目前正在此 VM 中運行的程序的一個已註釋元素。該接口允許反射性地讀取註釋。由此接口中的方法返回的所有註釋都是不可變並且可序列化的。調用者可以修改已賦值數組枚舉成員的訪問器返回的數組;這不會對其他調用者返回的數組產生任何影響。

如果此接口中的方法返回的註釋(直接或間接地)包含一個已賦值的 Class 成員,該成員引用了一個在此 VM 中不可訪問的類,則試圖通過在返回的註釋上調用相關的類返回的方法來讀取該類,將導致一個 TypeNotPresentException。

isAnnotationPresent
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

如果指定類型的註釋存在於此元素上,則返回 true,否則返回 false。此方法主要是爲了便於訪問標記註釋而設計的。

參數:

annotationClass - 對應於註釋類型的 Class 對象

返回:

如果指定註釋類型的註釋存在於此對象上,則返回 true,否則返回 false

拋出:

NullPointerException - 如果給定的註釋類爲 null

從以下版本開始:

1.5

getAnnotation
<T extends Annotation> T getAnnotation(Class<T> annotationClass)

如果存在該元素的指定類型的註釋,則返回這些註釋,否則返回 null。

參數:

annotationClass - 對應於註釋類型的 Class 對象

返回:

如果該元素的指定註釋類型的註釋存在於此對象上,則返回這些註釋,否則返回 null

拋出:

NullPointerException - 如果給定的註釋類爲 null

從以下版本開始:

1.5

getAnnotations
Annotation[] getAnnotations()

返回此元素上存在的所有註釋。(如果此元素沒有註釋,則返回長度爲零的數組。)該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。

返回:

此元素上存在的所有註釋

從以下版本開始:

1.5

getDeclaredAnnotations
Annotation[] getDeclaredAnnotations()

返回直接存在於此元素上的所有註釋。與此接口中的其他方法不同,該方法將忽略繼承的註釋。(如果沒有註釋直接存在於此元素上,則返回長度爲零的一個數組。)該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。

返回:

直接存在於此元素上的所有註釋

從以下版本開始:

1.5


下面是使用反射讀取RUNTIME保留策略的Annotation信息的例子:

自定義註解:

@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotation {

    String[] value1() default "abc";
}

使用自定義註解:

public class AnnotationTest2 {    @MyAnnotation(value1={"a","b"})
    @Deprecated
    public void execute(){
        System.out.println("method");
    }
}

讀取註解中的信息:

public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
    AnnotationTest2 annotationTest2 = new AnnotationTest2();    //獲取AnnotationTest2的Class實例
    Class<AnnotationTest2> c = AnnotationTest2.class;    //獲取需要處理的方法Method實例
    Method method = c.getMethod("execute", new Class[]{});    //判斷該方法是否包含MyAnnotation註解
    if(method.isAnnotationPresent(MyAnnotation.class)){        //獲取該方法的MyAnnotation註解實例
        MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);        //執行該方法
        method.invoke(annotationTest2, new Object[]{});        //獲取myAnnotation
        String[] value1 = myAnnotation.value1();
        System.out.println(value1[0]);
    }    //獲取方法上的所有註解
    Annotation[] annotations = method.getAnnotations();    for(Annotation annotation : annotations){
        System.out.println(annotation);
    }
}

8、限定註解的使用:

限定註解使用@Target。

@Documented
@Retention(value=RUNTIME)
@Target(value=ANNOTATION_TYPE)
public @interface Target

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

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

此元註釋指示該聲明類型只可作爲複雜註釋類型聲明中的成員類型使用。它不能直接用於註釋:

@Target({}) 
    public @interface MemberType {
        ...
    }

這是一個編譯時錯誤,它表明一個 ElementType 常量在 Target 註釋中出現了不只一次。例如,以下元註釋是非法的:

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
    public @interface Bogus {
        ...
    }
public enum ElementTypeextends Enum<ElementType>

程序元素類型。此枚舉類型的常量提供了 Java 程序中聲明的元素的簡單分類。

這些常量與 Target 元註釋類型一起使用,以指定在什麼情況下使用註釋類型是合法的。

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


註解的使用限定的例子:

@Target(ElementType.METHOD)public @interface MyAnnotation {

    String[] value1() default "abc";
}

9、在幫助文檔中加入註解:

要想在製作JavaDoc文件的同時將註解信息加入到API文件中,可以使用java.lang.annotation.Documented。

在自定義註解中聲明構建註解文檔:

@Documentedpublic @interface MyAnnotation {

    String[] value1() default "abc";
}

使用自定義註解:

public class AnnotationTest2 {    @MyAnnotation(value1={"a","b"})
    public void execute(){
        System.out.println("method");
    }
}

10、在註解中使用繼承:

默認情況***解並不會被繼承到子類中,可以在自定義註解時加上java.lang.annotation.Inherited註解聲明使用繼承。

@Documented
@Retention(value=RUNTIME)
@Target(value=ANNOTATION_TYPE)
public @interface Inherited

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

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

除了文章中有特別說明,均爲IT宅原創文章,轉載請以鏈接形式註明出處。
本文鏈接:http://www.itzhai.com/java-based-notebook-annotation-annotation-introduction-and-use-custom-annotations.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章