天天看點

注解Annotation實作原理與自定義注解淺析

什麼是注解?

對于很多初次接觸的開發者來說應該都有這個疑問?Annontation是Java5開始引入的新特征,中文名稱叫注解。它提供了一種安全的類似注釋的機制,用來将任何的資訊或中繼資料(metadata)與程式元素(類、方法、成員變量等)進行關聯。為程式的元素(類、方法、成員變量)加上更直覺更明了的說明,這些說明資訊是與程式的業務邏輯無關,并且供指定的工具或架構使用。Annontation像一種修飾符一樣,應用于包、類型、構造方法、方法、成員變量、參數及本地變量的聲明語句中。

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

注解的用處:

1、生成文檔。這是最常見的,也是java 最早提供的注解。常用的有@param @return 等

2、跟蹤代碼依賴性,運作時動态處理,擷取資訊,實作替代配置檔案功能。比如Dagger 2 依賴注入,未來java 開發,将大量注解配置,具有很大用處;

3、在編譯時進行格式檢查。如@override 放在方法前,如果你這個方法并不是覆寫了超類方法,則編譯時就能檢查出。

注解的原理:

注解Annotation實作原理與自定義注解淺析

Java也為我們提供了一些常用的注解:

@Override,代表某方法是重寫父類中的方法
@Deprecated,表示被标記的内容已經過時、不建議被使用,如果被使用編譯器會報警告,但程式也能正常運作。
@SuppressWarnings,由于内容被@Deprecated标記後,編譯器會有警告,如果想忽略警告可以使用@SuppressWarnings
           

自定義注解::

自定義注解類編寫的一些規則:

  1. Annotation 型定義為@interface, 所有的Annotation 會自動繼承java.lang.Annotation這一接口,并且不能再去繼承别的類或是接口.
  2. 參數成員隻能用public 或預設(default) 這兩個通路權修飾
  3. 參數成員隻能用基本類型byte、short、char、int、long、float、double、boolean八種基本資料類型和String、Enum、Class、annotations等資料類型,以及這一些類型的數組.
  4. 要擷取類方法和字段的注解資訊,必須通過Java的反射技術來擷取 Annotation 對象,因為你除此之外沒有别的擷取注解對象的方法
  5. 注解也可以沒有定義成員,,不過這樣注解就沒啥用了

    PS:自定義注解需要使用到元注解

如何定義一個注解呢?可以先看看這些已有的注解是如何實作的,我們先以

@Override

為例:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
           

可以看到注解是通過

@interface

關鍵字來定義的,和接口的定義類似,但是又多了

@Target()

@Retention()

,這些是java中的元注解,元注解可以了解為内置的基礎注解,用來限定、說明自定義注解。除了這兩個元注解外,還有三個元注解

@Inherited

@Repeatable

@Documented

,後邊會詳細解釋這些元注解的作用!

再看看

@SuppressWarnings

注解的實作:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}
           

@Override

相比最大的差別就是注解體中多了

String[] value();

,它代表注解的屬性!關于注解屬性也會在後邊詳細的介紹!

是以定義注解時,除了使用

@interface

還需要考慮元注解、注解屬性,一個自定義注解的僞碼如下:

@元注解0
@元注解1
@元注解2
public @interface 注解名稱 {
    類型 attr0();
    類型 attr1();
}
           

元注解:

java.lang.annotation 提供了四種元注解,專門注解其他的注解(在自定義注解的時候,需要使用到元注解):

@Documented – 注解是否将包含在JavaDoc中

@Retention – 什麼時候使用該注解

@Target – 注解用于什麼地方

@Inherited – 是否允許子類繼承該注解

1.)@Retention – 定義該注解的生命周期

● RetentionPolicy.SOURCE : 在編譯階段丢棄。這些注解在編譯結束之後就不再有任何意義,是以它們不會寫入位元組碼。@Override, @SuppressWarnings都屬于這類注解。

● RetentionPolicy.CLASS : 在類加載的時候丢棄。在位元組碼檔案的進行中有用。注解預設使用這種方式

● RetentionPolicy.RUNTIME : 始終不會丢棄,運作期也保留該注解,是以可以使用反射機制讀取該注解的資訊。我們自定義的注解通常使用這種方式。

2.)Target – 表示該注解用于什麼地方。預設值為任何元素,表示該注解用于什麼地方。可用的ElementType 參數包括

● ElementType.CONSTRUCTOR: 用于描述構造器

● ElementType.FIELD: 成員變量、對象、屬性(包括enum執行個體)

● ElementType.LOCAL_VARIABLE: 用于描述局部變量

● ElementType.METHOD: 用于描述方法

● ElementType.PACKAGE: 用于描述包

● ElementType.PARAMETER: 用于描述參數

● ElementType.TYPE: 用于描述類、接口(包括注解類型) 或enum聲明

3.)@Documented – 一個簡單的Annotations 标記注解,表示是否将注解資訊添加在java 文檔中。

4.)@Inherited – 定義該注釋和子類的關系

@Inherited 元注解是一個标記注解,@Inherited 闡述了某個被标注的類型是被繼承的。如果一個使用了@Inherited 修飾的annotation 類型被用于一個class,則這個annotation 将被用于該class 的子類。

例子:

@Retention(RetentionPolicy.RUNTIME) // 注解會在class位元組碼檔案中存在,在運作時可以通過反射擷取到
@Target({ElementType.FIELD,ElementType.METHOD})//定義注解的作用目标**作用範圍字段、枚舉的常量/方法
@Documented//說明該注解将被包含在javadoc中
@Inherited //這個注解将被用于該class 的子類
public @interface FieldMeta {
 
	/**
	 * 注解屬性:下面介紹
	 * @return
	 */
	boolean id() default false;
	/**
	 * 注解屬性:字段名稱,default 為預設
	 * @return
	 */
	String name() default "";
}

           

注解屬性

在注解中定義屬性和在接口中定義方法的格式類似,例如:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String name();
    int age() default 18;
    String[] favour();
}
           

這樣就給Test注解定義了name、age兩個屬性,并用default關鍵字指定age的預設值為18。可以這樣使用定義好的注解:

@TestAnnotation(name = "Tom", age = 12, favour = {"music", "sports"})
public class Test {
}
           

由于age有預設值,可以在使用注解時不指定它的值。由于favour的類型為數組,是以當其有多個值時需要用{}包起來。

如果自定義注解沒有屬性或者屬性有預設值,則使用時可以直接寫@TestAnnotation,省略後邊的括号。

注解的屬性支援的資料類型如下:

  • 基本類型(byte、short、int、float、double、long、char、boolean),不包括其對應的包裝類型
  • String
  • . Class,即Class<?>
  • enum,例如enum staus {A, B, C}
  • 注解,例如Override test();
    及上述類型對應的數組
               

注解相關的文法糖就介紹到這裡了,接下來要關注的是當一個類、方法、屬性等使用了注解後,如何提取注解上的資訊。

注解與反射

要提取注解上的資訊,就要用到反射相關的知識了,下面看一個完整的例子,首先定義TestAnnotation注解,可以作用在類、字段、方法聲明的地方,并可以在運作時被擷取,以及三個屬性:

@Target(value = {ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String name();
    int age() default 18;
    String[] favour();
}
           

在Test的類、字段、方法聲明的地方分别使用TestAnnotation注解:

@TestAnnotation(name = "Test", age = 20, favour = {"music", "sports"})
public class Test {

    @TestAnnotation(name = "testField", favour = {"reading", "sports"})
    private int testField;

    @TestAnnotation(name = "testMethod", age = 10, favour = {"dancing", "music"})
    public void testMethod() {

    }

    @TestAnnotation(name = "testMethod1", age = 12, favour = {"music"})
    public void testMethod1() {

    }
}
           

通過反射的方式提取注解資訊:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        resolve();
    }

    private void resolve() {
        // 解析類上的注解
        boolean isPresent = Test.class.isAnnotationPresent(TestAnnotation.class);
        if (isPresent) {
            TestAnnotation annotation = Test.class.getAnnotation(TestAnnotation.class);
            showAnnotation(annotation);
        }
        // 解析字段上的注解
        Field[] fields = Test.class.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(TestAnnotation.class)) {
                TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
                showAnnotation(annotation);
            }
        }
        // 解析方法上的注解
        Method[] methods = Test.class.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(TestAnnotation.class)) {
                TestAnnotation annotation = method.getAnnotation(TestAnnotation.class);
                showAnnotation(annotation);
            }
        }
    }

    private void showAnnotation(TestAnnotation annotation) {
        Log.e("Annotation", annotation.name() + "#" + annotation.age() + "#" + Arrays.toString(annotation.favour()));
    }
}
           

運作後的效果如下:

注解Annotation實作原理與自定義注解淺析

其中涉及到了幾個關鍵的方法,Class、Method、Field等類都有這樣的方法:

boolean isAnnotationPresent(Class<? extends Annotation> annotation),用來判斷是否使用了某個注解。

public <A extends Annotation> A getAnnotation(Class<A> annotation),獲得指定名稱的注解對象。

public Annotation[] getAnnotations(),傳回對應元素的全部注解。

public Annotation[] getDeclaredAnnotations(),傳回直接在對應元素上使用的注解,不包括父類的注解。

boolean isAnnotationPresent(Class<? extends Annotation> annotation),用來判斷是否使用了某個注解。

public <A extends Annotation> A getAnnotation(Class<A> annotation),獲得指定名稱的注解對象。

public Annotation[] getAnnotations(),傳回對應元素的全部注解。

public Annotation[] getDeclaredAnnotations(),傳回直接在對應元素上使用的注解,不包括父類的注解。