通過反射獲取注解資訊

2020-07-16 10:05:17
使用註解修飾了類、方法、變數等成員之後,這些註解不會自己生效,必須由開發者提供相應的工具來提取處理。要想獲取類、方法或變數的注解資訊,必須通過 Java 的反射技術來獲取 Annotation 物件,除此之外沒有其它方法。

下面主要介紹 Java 基於反射機制獲取註解的方法,並以結合範例的方式講解了獲取注解資訊的具體實現方法與操作事項。

所有 Java 註解型別都繼承於 java.lang.annotation.Annotation 介面,該介面代表程式中可以接收註解的程式元素。該介面主要有如下幾個實現類。
  1. Class:類定義。
  2. Constructor:構造方法定義。
  3. Field:類的成員變數定義。
  4. Method:類的方法定義。
  5. Package:類的包定義。

java.lang.reflect 包下主要包含一些實現反射功能的工具類,從 Java 5 開始,java.lang.reflect 包所提供的反射 API 增加了讀取執行時註解的能力。只有當定義注解時使用了 @Retention(RetentionPolicy.RUNTIME) 修飾,該注解才會在執行時可見。

AnnotatedElement 介面是所有程式元素(如 Class、Method、Constructor 等)的父介面,所以程式通過反射獲取了某個類的 AnnotatedElement 物件(如 Class、Method、 Constructor 等)之後,程式就可以呼叫該物件的如下幾個方法來存取注解資訊,方法名稱及作用如下表所示。

方法名 作用
<A extends Annotation> A getAnnotation(Class<A> annotationClass) 如果該元素存在 annotationClass 型別的註解,則返回註解,否則返回 null
<A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass) 這是 Java 8 新增的方法,該方法嘗試獲取直接修飾該程式元素、指定型別的註解。如果該型別的註解不存在,則返回 null
Annotation[] getAnnotations() 返回該元素上存在的所有註解
Annotation[] getDeclaredAnnotations() 返回直接存在於該元素的所有註解(和 getAnnotations() 的區別在於該方法將不返回繼承的注釋)
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 判斷該元素上是否存在 annotationClass 型別的註解,如果存在則返回 true,否則返回 false。
<A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass) 該方法與前面介紹的 getAnnotation() 方法基本相似。但由於 Java 8 增加了重複註解功能,因此需要使用該方法獲取該元素存在 annotationClass 型別的多個註解。
<A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass) 該方法與前面介紹的 getDeclaredAnnotations() 方法基本相似。但由於 Java 8 增加了重複註解功能,因此需要使用該方法獲取該元素存在 annotationClass 型別的多個註解。
根據官方的命名規則,可以總結出以下幾條:
  1. getDeclaredAnnotationXXXX:只可以獲取直接存在的注解資訊,即直接修飾在某個元素(類、屬性和方法)上的註解。
  2. getXXXXByType:可以獲取間接存在的注解資訊
  3. getAnnotationXXX:可以獲取繼承的注解資訊

注意:所有的方法都可以獲取直接注解資訊

為了獲得程式中的元素(如類、方法等),必須使用反射知識,如果不了解 Java 反射,可先閱讀學習《Java反射機制是什麼?Java反射機制的基本概念》一節。

例? 1 

在 SpringMVC 中,經常使用 @RequestMapping(value="") 註解,這樣 Spring 就會將我們填寫的 value 值當作路徑存放在 map 結構中讓我們存取。下面我們使用 getAnnotation() 方法來實現這個功能,程式碼如下。
/**
* 這是自定義注解的類
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
    String value(); // 這是註解的一個屬性欄位,也就是在使用註解時填寫在括號裡的引數
}
建立 TestController 類,然後在 TestController 類中使用 @MyRequestMapping 註解,程式碼如下:
@MyRequestMapping("/test")
public class TestController {
    public void test() {
        System.out.println("進入Test方法");
    }
}
測試類程式碼如下:
public class Test {
    public static void main(String[] args) {
        Class<?> c = TestController.class;
        MyRequestMapping baseRequestMapping = c.getAnnotation(MyRequestMapping.class);
        System.out.println(baseRequestMapping.value()); // 輸出value的值
    }
}
輸出結果為/test

注意:Class<?> 中的 <?> 是必須寫的,你可以把?改成要限定的型別,但是必須要寫這個泛型限定才行,否則會編譯錯誤。

例 2

下面我們使用 getAnnotations() 方法獲取多個註解,並輸出。

1)自定義 Person 註解,程式碼如下:
@Target({ ElementType.TYPE })
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Person {
    String value();
}
2)修改例 1 中的 TestController 類,程式碼如下:
@MyRequestMapping("/test")
@Person("C")
public class TestController {
    public void test() {
        System.out.println("進入Test方法");
    }
}
3)建立測試類,獲取 TestController 類裡的所有註解,並將這些註解列印出來。程式碼如下所示:
public class Test {
    public static void main(String[] args) {
        Class<?> c = TestController.class;
        Annotation[] atnsArray = c.getAnnotations();
        for (Annotation an : atnsArray) {
            System.out.println(an);
        }
    }
}
輸出結果為:

@MyRequestMapping(value=/test)
@Person(value=C)