1. 程式人生 > 其它 >淺析Java註解的意義、分類、用途、如何自定義註解使用示例及Java註解的實現原理的分析

淺析Java註解的意義、分類、用途、如何自定義註解使用示例及Java註解的實現原理的分析

  今天將從以下4個方面來系統的學習一下java註解:什麼是註解、註解的用途、註解使用演示、註解的實現原理。

一、什麼是註解

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

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

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

2、一類為元註解,元註解是用於定義註解的註解

,包括@Retention(標明註解被保留的階段)、@Target(標明註解使用的範圍)、@Inherited(標明註解可繼承)、@Documented(標明是否生成javadoc文件)

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

二、註解的用途

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

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

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

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

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

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

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

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

三、註解使用演示

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

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

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

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

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

  具體程式碼如下:

// 定義一個可以註解在Class,interface,enum上的註解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnTargetType { // 定義註解的一個元素 並給定預設值 String value() default "我是定義在類、介面、列舉類上的註解元素value的預設值"; } // 定義一個可以註解在METHOD上的註解 @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnTargetMethod { // 定義註解的一個元素 並給定預設值 String value() default "我是定義在方法上的註解元素value的預設值"; } // 定義一個可以註解在FIELD上的註解 @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnTargetField { String value() default "我是定義在欄位上的註解元素value的預設值"; } // 定義一個可以註解在PARAMETER上的註解 @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnTargetParameter { 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的預設值

四、註解的實現原理

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

讓我們先看一下實現註解三要素:註解宣告、使用註解的元素、操作註解使其起作用(註解處理器)

1、註解宣告

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

@Target,
@Retention,
@Documented,
@Inherited

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

(1)@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();
}
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,再看看它的原始碼。ElementType是一個列舉類,定義了註解可以作用的型別,上面例子中演示了TYPE,FIELD,METHOD,PARAMETER 4種可以作用的目標。

(2)@Retention定義了該Annotation被保留的時間長短。

  某些Annotation僅出現在原始碼中,而被編譯器丟棄;

  而另一些卻被編譯在class檔案中;編譯在class檔案中的Annotation可能會被虛擬機器忽略,

  而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因為Annotation與class在使用上是被分離的)。

  使用這個元註解可以對 Annotation的“生命週期”進行限制。

(3)@Documented:@Documented用於描述其它型別的annotation應該被作為被標註的程式成員的公共API,因此可以被例如javadoc此類的工具文件化。

  Documented是一個標記註解,沒有成員。

(4)@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.test.run.ChildAnnotationTest.main(ChildAnnotationTest.java:17)

2、使用註解的元素

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

3、註解處理器

  這個是註解使用的核心了,前面我們說了那麼多註解相關的,那到底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)方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回改程式元素上存在的、指定型別的註解,如果該型別註解不存在,則返回null。

(2)方法2:Annotation[] getAnnotations():返回該程式元素上存在的所有註解。

(3)方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程式元素上是否包含指定型別的註解,存在則返回true,否則返回false.

(4)方法4:Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的所有註釋。與此介面中的其他方法不同,該方法將忽略繼承的註釋。(如果沒有註釋直接存在於此元素上,則返回長度為零的一個數組。)該方法的呼叫者可以隨意修改返回的陣列;這不會對其他呼叫者返回的陣列產生任何影響

轉載於 一個懶惰的程式設計師 的文章:https://mp.weixin.qq.com/s?__biz=MzAxMjY1NTIxNA==&mid=2454441897&idx=1&sn=729688d470c94560c1e73e79f0c13adc&chksm=8c11e0a8bb6669be1cc4daee95b221ba437d536d598520d635fac4f18612dded58d6fddb0dce&scene=21#wechat_redirect