Android Annotation-讓你的代碼和設計更加優雅(一)

引子

最近我寫了一篇關於組件化的開源框架源碼分析的文章(傳送門在下面兒)。那麼如今組件化小有名氣的JIMU框架,也是我下一個要給你們分享的源碼分析文章。但由於其中涉及到了不少Java Annotation相關的知識。因此不得不在這裏,先安利一下本篇,這也是本篇的由來。java

優秀框架源碼分析系列(一)讓解耦更輕鬆!多進程組件化框架-ModularizationArchitectureweb

註解」,在Java世界裏隨處可見,但一般狀況下,多數人對其是視而不見的。但當咱們設計SDK,設計基礎庫的時候,運用註解,能夠起到簡化配置的做用。熟悉ButterKnife的朋友都知道,它就是經過註解來在編譯期間,增長Java代碼來實現的。若是你還不知道它是如何實現的,那麼相信你食用完本篇和下一篇之後,就會明白這一切了。編程

食用路線

學習新知識的時候,要掌握正確的進食方法,腦子裏必須先對知識結構有預期,學習完以後再回顧結構,根據結構記住知識。本篇將按照導圖的結構,來進行講解。數組

紮實打基礎

基礎知識-四大元註解

元註解,就是用來修飾註解的註解。bash

@Target(value=ElementType)

@Target被用來指明此Annotation所修飾的對象範圍(即:被描述的註解能夠用在什麼地方)。Java中,註解(Annotation)可被用於如下位置 :markdown

  • package、types(類、接口、枚舉、Annotation類型)
  • 類型成員(方法、構造方法、成員變量、枚舉值)
  • 方法參數和本地變量(如循環變量、catch參數)

註解的使用範圍,經過Target的取值來指定。指定好之後,@Target修飾的元素必定是與其取的value相匹配的,不然編譯會報錯。框架

value取值(ElementType)常見的有:ide

ElementType 含義
CONSTRUCTOR 用於描述構造器
FIELD 用於描述域(包括enum常量)
LOCAL_VARIABLE 用於描述局部變量
METHOD 用於描述方法
PACKAGE 用於描述包
PARAMETER 用於描述參數
TYPE 用於描述類、接口(包括註解類型) 或enum聲明

注:PACKAGE,它並非使用在通常的類中,而是用在固定的文件package-info.java中。這裏須要強調命名必定是「package-info」工具

這裏須要特殊說明的是,在之前的Java版本中,開發者只能將註解(Annotation)寫在聲明中。但從Java 8開始,註解能夠寫在使用類型的任何地方,例如聲明、泛型和強制類型轉換等語句:oop

@Encrypted String str;
List<@NonNull String> strs;
test = (@Immutable Test) tmpTest;
複製代碼

針對這個拓展,JAVA8對原有的@Target的取值作了擴充,引入了新的類型(TYPE)註解,即ElmentType增長了:

ElementType 含義
TYPE_PARAMETER 表示註解能夠用在類型變量的聲明語句中(如 class Test {...})
TYPE_USE 表示註解能夠用在使用類型的任何語句中(如聲明語句、泛型和強轉)

關於類型的解釋參考上文。

@Retention(value=RetentionPolicy)

@Retention,翻譯爲保留,指示了一個註解被保留的時間期限,一個被其修飾的註解會被保留到其value指定三個階段的其中之一,若是註解類型聲明中不存在Retention註解,則保留策略默認爲CLASS

RetentionPolicy 含義
RetentionPolicy.SOURCE 只在源代碼級別保留,編譯時就會被忽略
RetentionPolicy.CLASS 在編譯時被保留,在class文件中存在,但JVM將會忽略
RetentionPolicy.RUNTIME 被JVM保留,因此他能在運行時被JVM或其餘使用反射機制的代碼所讀取和使用

@Documented

被@Documented修飾的註解,用來表示這個被修飾註解應該被 javadoc工具記錄。默認狀況下,javadoc是不包括註解的。但若是聲明註解時指定了@Documented,則它會被javadoc之類的工具處理,因此註解類型信息也會被包括在生成的文檔中。

@Inherited

若是一個用來修飾class的註解,但願被這個class的sub-class繼承,則能夠對這個註解使用@Inherited修飾。 上面這句話強調了如下兩點:

  • 註解的可繼承性。當自定義的註解但願被繼承時,就要使用@Inherited修飾
  • @Inherited只在修飾class時有效,修飾其餘類型時無效

自定義註解如何寫-Java Annotation的語法

自定義註解,經過@符號和interface關鍵字來定義。相似於class的寫法,例如:

package com.xm.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnno{
    String name();
    String website() default "example.com";
    int version() default 1;
}
複製代碼

當註解須要參數的時候,咱們須要先定義註解的方法,方法名即參數名。這裏有點相似於接口的定義,若是參數須要默認值,則在方法後加default + 默認值的方式來實現。

例如本例中,咱們指定了三個參數,name、website、version。分別定義了三個方法,name沒有指定默認值,因此它默認爲null,website指定了默認值爲example.com,而version則指定了1爲其默認值。

這裏有三點規則強調一下:

  1. 註解方法不帶參數,好比name()website()
  2. 註解方法返回值類型:基本類型、String、Enums、Annotation以及前面這些類型的數組類型
  3. 註解方法可有默認值,好比default "example.com",默認website=」example.com

當咱們在使用時,須要給參數傳遞值,很簡單:

@TestAnno(name="xiaoming", website="example.com", version=1)
複製代碼

後文對這個例子中的其餘部分,還會有詳細的解釋。

高效學精髓

爲了方便學習,咱們拿最多見的Java內建的註解@Override來食用。把握一下自定義註解實現時的幾個步驟。

1. 編寫定義註解的Java文件

要自定義註解,首先要建立一個以註解名字命名的Java文件,並使用@interface關鍵字來定義註解

Override.java

package java.lang

public @interface Override {
}
複製代碼

2. 肯定自定義註解的做用範圍

這個也很容易理解,Override註解用來修飾方法,因此做用範圍就指定爲METHOD。

3. 肯定自定義註解的做用時限

SOURCE、CLASS、RUNTIME的保留時限依次增長。而對於Override來講,咱們平常編寫代碼時,經過這個註解能夠知道哪些方法是被重寫的。這個提示,也僅僅停留在了源碼層面,因此這裏使用SOURCE。

4. 肯定自定義註解的參數、方法

Override並無使用任何的參數和方法,這裏也忽略了,後面咱們實戰例子裏會重點介紹。

基於上述分析,咱們寫出了以下的定義源碼:

package java.lang;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
複製代碼

經過以上的三個步驟,咱們就定義好了一個註解。當咱們使用時,直接經過@符號加註解名稱便可。那麼,加了註解的元素,有什麼用呢?

其一,咱們能夠經過編譯期處理,爲註解所在的元素自動生成其餘代碼。像ButterKnife等均是如此,它幫助咱們生成了View與組件的綁定,實現了點擊監聽器的綁定等等。保留到此時限的註解,咱們也稱其爲編譯期註解。

其二,咱們在運行時,能夠經過反射,獲取到註解信息,這些註解信息,每每是程序編寫者但願傳遞給運行時使用的一些信息或配置,能夠起到簡化配置的做用。像Spring2.5之後,運用了大量的運行時註解,它在實現AOP方面,有着普遍的應用。須要強調的是,這裏的註解,都是RUNTIME規則的,只有這樣才能保留到運行時。因此咱們稱其爲運行時註解。

本篇咱們重點介紹代碼編寫階段的提示性註解和運行時註解。下面咱們再經過兩個實際例子來理解一下。

大膽練實戰(一)

提示性註解示例

咱們在java中,除了Override,還會常常見到一些其餘的內建註解。例如SuppressWarnings(壓制警告),他用於告知編譯器忽略特定的警告信息,如在泛型中使用原始數據類型。

package java.lang;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.METHOD,
        ElementType.PARAMETER, ElementType.CONSTRUCTOR,
        ElementType.LOCAL_VARIABLE })
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    /** * The list of warnings a compiler should not issue. */
    public String[] value();
}
複製代碼

按咱們上節所需編寫自定義註解的幾個步驟,來分別分析一下:

註解的做用範圍

SuppressWarnings可用於除註解類型聲明和包名以外的全部元素。因此這裏的Target作了相應的指定。

註解的做用時限

這裏的警告,都是靜態語法檢查類型的警告信息,因此這個註解也只須要保留在源碼層面。即SOURCE

註解的參數方法

SuppressWarning指定了一個String類型的數組。它支持了多個字符串參數。其可取值爲須要壓制的警告類型,見下表:

參數 含義
deprecation 使用了過期的類或方法時的警告
unchecked 執行了未檢查的轉換時的警告
fallthrough 當Switch程序塊進入進入下一個case而沒有Break時的警告
path 在類路徑、源文件路徑等有不存在路徑時的警告
serial 當可序列化的類缺乏serialVersionUID定義時的警告
finally 任意finally子句不能正常完成時的警告
all 以上全部狀況的警告

使用時,可按以下的方法賦值:

@SupressWarning(value={"uncheck","deprecation"})
複製代碼

運行時註解示例

針對咱們上一節中自定義的註解TestAnno,咱們來實踐一下運行時註解。

準備工做

package com.xm.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnno{
    String name();
    String website() default "example.com";
    int version() default 1;
}
複製代碼

假設咱們在以下的代碼塊中應用了此註解:

package com.xm.annotation;

public class AnnotationDemo {
    @AuthorAnno(name="xiaoming", website="example.com", version=1)
    public static void main(String[] args) {
        System.out.println("I am main method");
    }

    @SuppressWarnings({ "unchecked", "deprecation" })
    @AuthorAnno(name="suby", website="example2.com", version=2)
    public void demo(){
        System.out.println("I am demo method");
    }
}
複製代碼

針對註解解析

如今,咱們在運行時,經過反射來解析自定義的註解@TestAnno,關於反射類位於包java.lang.reflect,其中有一個接口AnnotatedElement,該接口定義了註釋相關的幾個核心方法,以下:

返回值 方法 含義
T getAnnotation(Class annotationClass) 當存在該元素的指定類型註解,則返回相應註釋,不然返回null
Annotation[] getAnnotations() 返回此元素上存在的全部註解
Annotation[] getDeclaredAnnotations() 返回直接存在於此元素上的全部註解
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 當存在該元素的指定類型註解,則返回true,不然返回false

前面咱們自定義的註解,適用對象爲Method。類Method繼承類AccessibleObject,而類AccessibleObject實現了AnnotatedElement接口,那麼能夠利用上面的反射方法,來實現解析@TestAnno的功能(AnnotationParser.java),內容以下:

package com.xm.annotation;
import java.lang.reflect.Method;

public class AnnotationParser {
    public static void main(String[] args) throws SecurityException, ClassNotFoundException {
        String clazz = "com.xm.annotation.AnnotationDemo";
        Method[]  demoMethod = AnnotationParser.class
                .getClassLoader().loadClass(clazz).getMethods();

        for (Method method : demoMethod) {
            if (method.isAnnotationPresent(TestAnno.class)) {
                 AuthorAnno authorInfo = method.getAnnotation(TestAnno.class);
                 System.out.println("method: "+ method);
                 System.out.println("name= "+ authorInfo.name() +
                         " , website= "+ authorInfo.website()
                        + " , version= "+authorInfo.version());
            }
        }
    }
}
複製代碼

程序的輸出結果:

method: public void com.xm.annotation.AnnotationDemo.demo()
name= suby , website= example2.com , version= 2
method: public static void com.xm.annotation.AnnotationDemo.main(java.lang.String[])
name= xiaoming , website= example.com , version= 1
複製代碼

因而可知,咱們能夠經過在編寫代碼時,將一些運行時須要的信息,經過註解的方式傳遞給運行時的代碼,以達到信息傳遞和配置的目的。在Spring中,也大量採用了運行時註解,爲程序的配置,以及程序開發,特別是AOP編程,都提供了極大的便利。

下一篇重點介紹編譯期註解,在衆多的知名框架或工具型SDK中,都能看見它的身影,它能夠以一種極度優雅簡潔的方式,爲咱們提供開發上的便捷

小銘出品,必屬精品

歡迎關注xNPE技術論壇,更多原創乾貨每日推送。

相關文章
相關標籤/搜索