1. 程式人生 > >認識下java註解的實現原理

認識下java註解的實現原理

1,什麼是註解

註解也叫元資料,例如常見的@Override和@Deprecated,註解是JDK1.5版本開始引入的一個特性,用於對程式碼進行說明,可以對包、類、介面、欄位、方法引數、區域性變數等進行註解

一般常用的註解可以分為三類:

  1. 一類是Java自帶的標準註解,包括@Override(標明重寫某個方法)、@Deprecated(標明某個類或方法過時)和@SuppressWarnings(標明要忽略的警告),使用這些註解後編譯器就會進行檢查。

  2. 一類為元註解,元註解是用於定義註解的註解,包括@Retention(標明註解被保留的階段)、@Target(標明註解使用的範圍)、@Inherited(標明註解可繼承)、@Documented(標明是否生成javadoc文件)

  3. 一類為自定義註解,可以根據自己的需求定義註解

 

2,註解的用途

在看註解的用途之前,有必要簡單的介紹下XML和註解區別,

註解:是一種分散式的元資料,與原始碼緊繫結

xml:是一種集中式的元資料,與原始碼無繫結

當然網上存在各種XML與註解的辯論哪個更好,這裡不作評論和介紹,主要介紹一下註解的主要用途:

  1. 生成文件,通過程式碼裡標識的元資料生成javadoc文件。

  2. 編譯檢查,通過程式碼裡標識的元資料讓編譯器在編譯期間進行檢查驗證。

  3. 編譯時動態處理,編譯時通過程式碼裡標識的元資料動態處理,例如動態生成程式碼。

  4. 執行時動態處理,執行時通過程式碼裡標識的元資料動態處理,例如使用反射注入例項

 

3,註解使用演示

這邊總共定義了4個註解來演示註解的使用

  1. 定義一個可以註解在Class,interface,enum上的註解,

  2. 定義一個可以註解在METHOD上的註解

  3. 定義一個可以註解在FIELD上的註解

  4. 定義一個可以註解在PARAMETER上的註解

具體程式碼如下:

/**
* 定義一個可以註解在Class,interface,enum上的註解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetType {
   /**
    * 定義註解的一個元素 並給定預設值
    * @return
    */
   String value() default "我是定義在類介面列舉類上的註解元素value的預設值";
}

 

/**
* 定義一個可以註解在METHOD上的註解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetMethod {
   /**
    * 定義註解的一個元素 並給定預設值
    * @return
    */
   String value() default "我是定義在方法上的註解元素value的預設值";
}

 

/**
* 定義一個可以註解在FIELD上的註解
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetField {
   /**
    * 定義註解的一個元素 並給定預設值
    * @return
    */
   String value() default "我是定義在欄位上的註解元素value的預設值";
}

 

/**
* 定義一個可以註解在PARAMETER上的註解
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetParameter {
   /**
    * 定義註解的一個元素 並給定預設值
    * @return
    */
   String value() default "我是定義在引數上的註解元素value的預設值";
}

 

編寫一個測試處理類處理以上註解,這邊不過多解釋,程式碼中都寫了對應的註釋如下:

/**
* 測試java註解類
*/
@MyAnTargetType
public class AnnotationTest {
   @MyAnTargetField
   private String field = "我是欄位";
   @MyAnTargetMethod("測試方法")
   public void test(@MyAnTargetParameter String args) {
       System.out.println("引數值 === "+args);
   }
   public static void main(String[] args) {
       // 獲取類上的註解MyAnTargetType
       MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);
       System.out.println("類上的註解值 === "+t.value());
       MyAnTargetMethod tm = null;
       try {
           // 根據反射獲取AnnotationTest類上的test方法
           Method method = AnnotationTest.class.getDeclaredMethod("test",String.class);
           // 獲取方法上的註解MyAnTargetMethod
           tm = method.getAnnotation(MyAnTargetMethod.class);
           System.out.println("方法上的註解值 === "+tm.value());
           // 獲取方法上的所有引數註解  迴圈所有註解找到MyAnTargetParameter註解
           Annotation[][] annotations = method.getParameterAnnotations();
           for(Annotation[] tt : annotations){
               for(Annotation t1:tt){
                   if(t1 instanceof MyAnTargetParameter){
                       System.out.println("引數上的註解值 === "+((MyAnTargetParameter) t1).value());
                   }
               }
           }
           method.invoke(new AnnotationTest(), "改變預設引數");
           // 獲取AnnotationTest類上欄位field的註解MyAnTargetField
           MyAnTargetField fieldAn = AnnotationTest.class.getDeclaredField("field").getAnnotation(MyAnTargetField.class);
           System.out.println("欄位上的註解值 === "+fieldAn.value());
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
}

執行結果如下:

類上的註解值 === 我是定義在類介面列舉類上的註解元素value的預設值
引數上的註解值 === 我是定義在引數上的註解元素value的預設值
引數值 === 改變預設引數
方法上的註解值 === 測試方法
欄位上的註解值 === 我是定義在欄位上的註解元素value的預設值

 

4,註解的實現原理

以上只抽取了註解的其中幾種型別演示,下面來看看他們是怎麼工作的

先看一下實現註解三要素:

1,註解宣告、

2,使用註解的元素、

3,操作註解使其起作用(註解處理器)

註解宣告

首先讓看一下java中的元註解(也就是上面提到的註解的註解),總共有4個如下:

  • @Target,

  • @Retention,

  • @Documented,

  • @Inherited

這4個元註解都是在jdk的java.lang.annotation包下面

@Target

Target說明的是Annotation所修飾的物件範圍,原始碼如下:

@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();
}

其中只有一個元素ElementType,再看看它的原始碼如下:

public enum ElementType {
   /** Class, interface (including annotation type), or enum declaration */
   TYPE,
   /** Field declaration (includes enum constants) */
   FIELD,
   /** Method declaration */
   METHOD,
   /** Formal parameter declaration */
   PARAMETER,
   /** Constructor declaration */
   CONSTRUCTOR,
   /** Local variable declaration */
   LOCAL_VARIABLE,
   /** Annotation type declaration */
   ANNOTATION_TYPE,
   /** Package declaration */
   PACKAGE,
   /**
    * Type parameter declaration
    *
    * @since 1.8
    */
   TYPE_PARAMETER,
   /**
    * Use of a type
    *
    * @since 1.8
    */
   TYPE_USE
}

ElementType是一個列舉類定義註解可以作用的型別上,上面例子中演示了TYPE,FIELD,METHOD,PARAMETER 4種可以作用的目標

@Retention

定義了該Annotation被保留的時間長短:某些Annotation僅出現在原始碼中,而被編譯器丟棄;而另一些卻被編譯在class檔案中;編譯在class檔案中的Annotation可能會被虛擬機器忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因為Annotation與class在使用上是被分離的)。使用這個元註解可以對 Annotation的“生命週期”限制

@Documented

@Documented用於描述其它型別的annotation應該被作為被標註的程式成員的公共API,因此可以被例如javadoc此類的工具文件化。Documented是一個標記註解,沒有成員

@Inherited

@Inherited 元註解是一個標記註解,@Inherited闡述了某個被標註的型別是被繼承的。如果一個使用了@Inherited修飾的annotation型別被用於一個class,則這個annotation將被用於該class的子類

注意:@Inherited annotation型別是被標註過的class的子類所繼承。類並不從它所實現的介面繼承annotation,方法並不從它所過載的方法繼承annotation。

當@Inherited annotation型別標註的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼承性。如果使用java.lang.reflect去查詢一個@Inherited annotation型別的annotation時,反射程式碼檢查將展開工作:檢查class和其父類,直到發現指定的annotation型別被發現,或者到達類繼承結構的頂層。

 

寫了一個例子,在以上MyAnTargetType註解類中增加@Inherited註解,如下:

/**
* 定義一個可以註解在Class,interface,enum上的註解
* 增加了@Inherited註解代表允許繼承
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnTargetType {
   /**
    * 定義註解的一個元素 並給定預設值
    * @return
    */
   String value() default "我是定義在類介面列舉類上的註解元素value的預設值";
}

增加一個子類ChildAnnotationTest繼承AnnotationTest測試如下:

/**
* 增加一個子類繼承AnnotationTest 演示@Inherited註解允許繼承
*/
public class ChildAnnotationTest extends AnnotationTest {
   public static void main(String[] args) {
       // 獲取類上的註解MyAnTargetType
       MyAnTargetType t = ChildAnnotationTest.class.getAnnotation(MyAnTargetType.class);
       System.out.println("類上的註解值 === "+t.value());
   }
}

執行如下:

類上的註解值 === 我是定義在類介面列舉類上的註解元素value的預設值

說明已經獲取到了父類AnnotationTest的註解了

如果MyAnTargetType去掉@Inherited註解執行則報錯如下:

Exception in thread "main" java.lang.NullPointerException
   at com.zhang.run.ChildAnnotationTest.main(ChildAnnotationTest.java:17)

使用註解的元素

使用註解沒什麼好說的就是在你需要的地方加上對應的你寫好的註解就行

註解處理器

這個是註解使用的核心了,前面我們說了那麼多註解相關的,那到底java是如何去處理這些註解的呢

從getAnnotation進去可以看到java.lang.class實現了AnnotatedElement方法

MyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);
public final class Class<T> implements java.io.Serializable,
                             GenericDeclaration,
                             Type,
                             AnnotatedElement

java.lang.reflect.AnnotatedElement 介面是所有程式元素(Class、Method和Constructor)的父介面,所以程式通過反射獲取了某個類的AnnotatedElement物件之後,程式就可以呼叫該物件的如下四個個方法來訪問Annotation資訊:

  方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程式元素上存在的、指定型別的註解,如果該型別註解不存在,則返回null。
  方法2:Annotation[] getAnnotations():返回該程式元素上存在的所有註解。
   方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程式元素上是否包含指定型別的註解,存在則返回true,否則返回false.
  方法4:Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的所有註釋。與此介面中的其他方法不同,該方法將忽略繼承的註釋。(如果沒有註釋直接存在於此元素上,則返回長度為零的一個數組。)該方法的呼叫者可以隨意修改返回的陣列;這不會對其他呼叫者返回的陣列產生任何影響