Java 基礎(十七)註解

定義(What)

註解,是源代碼的元數據。java

兩個本質:android

  • 它就是一個附屬品,依賴於其餘元素存在
  • 自己沒有任何做用,在恰當的時候由外部程序解析產生做用。

做用(Why)

  • 簡化配置
  • 增長代碼可讀性
  • 提高系統可維護性

類型(How)

按定義分類:

分爲內置註解和自定義註解。spring

內置註解以下:數組

  • @Override
  • @Deprecated
  • @SuppressWarnings

自定義註解?android.support.annotation包下全是自定義註解,不知道你們注意過沒。貼個圖~緩存

按生命週期分類(重點)

  • SOURCE:表示在編譯時這個註解會被移除,不會包含在編譯後產生的 class 文件中
  • CLASS:邊上這個註解會被包含在 class 文件中,但運行時會被移除
  • RUNTIME:表示這個註解會被保留到運行時,在運行時能夠 JVM 訪問到,咱們能夠在運行時經過反射解析到這個註解。

可能有同窗會問:無論我是用於編譯時代碼生成仍是運行時反射處理,我直接對全部註解申明RetentionPolicy.RUNTIME不就行了嗎?或者即便我想在編譯時代碼生成我也用RetentionPolicy.SOURCE,也是能夠的吧?bash

沒錯,RetentionPolicy.RUNTIME是優先級最大的修飾,但爲何不建議呢?這個的緣由同修飾類成員時用的private仍是public得道理同樣。框架

元註解(重點)

磨刀不誤砍柴工,先弄清楚「元註解」,而後咱們再來學習自定義註解。ide

Retention定義註解生命週期,可選爲:source、class、runtime(生命週期介紹如上)工具

Documented 文檔化註解,會被 Javadoc 工具文檔化學習

Inherited註解自動集成的,想讓一個類和他的子類都包含某個註解,就可使用他來修飾這個註解。

Target說明了被修飾的註解的應用範圍,包括:

  • TYPE:表示能夠用來修飾類、接口、註解類型或枚舉類型
  • PACKAGE:能夠用來修飾包
  • PARAMETER:能夠用來修飾參數
  • ANNOTATION_TYPE:能夠用來修飾註解類型
  • METHOD:能夠用來修飾方法
  • FIELD:能夠用來修飾屬性(包含枚舉常量)
  • CONSTRUCTOR:能夠用來修飾構造器
  • LOCAL_VARLABLE:能夠用來修飾局部變量

敲黑板,Retention 和 Target 是重點,METHOD 是 Target 的重點。

自定義註解

首先咱們來建立一個註解類,其實就是一個接口前面加了一個@符號。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface CacheResult {

    String key();

    String cacheName();

    String backupKey() default "";//備份緩存

    boolean needBloomFilter() default false;//解決緩存擊穿

    boolean needLock() default false;//是否開啓讀取緩存線程鎖

}複製代碼

而後咱們用Retention給註解設置生命週期爲 Runtime,用 Target 爲註解設置這個註解能夠用來修飾類和方法。而後咱們給註解添加了五條屬性,其中前兩條沒有默認值(使用的時候須要手動設置,格式以下 demo)、後三條有默認值(可選設置值)。
以上註解是從 spring 框架中 copy 的,那5條屬性沒看懂沒事哈,都是自定義的,能夠隨便刪,咱們主要是學習註解怎麼用。

public class TestAnnotation {

    public static void main(String[] args) {
        Class<TestBean> testBeanClass = TestBean.class;
        Class<CacheResult> cacheResultClass = CacheResult.class;
        if (testBeanClass.isAnnotationPresent(cacheResultClass)) {
            CacheResult annotation = testBeanClass.getAnnotation(cacheResultClass);
            System.out.println(annotation.key());
            System.out.println(annotation.cacheName());
            Method[] methods = testBeanClass.getMethods();
            for (Method m : methods) {
                if (m.isAnnotationPresent(cacheResultClass)) {
                    CacheResult a = m.getAnnotation(cacheResultClass);
                    System.out.println(a.key());
                    System.out.println(a.cacheName());
                }
            }
        }
    }

    @CacheResult(key = "class", cacheName = "className")
    public class TestBean {

        @CacheResult(key = "method", cacheName = "methodName")
        public void test() {

        }
    }
}複製代碼

打印結果

class
className
method
methodName

Process finished with exit code 0複製代碼

經過以上 demo,咱們要記住註解的兩個本質:

  • 它就是一個附屬品,依賴於其餘元素存在
  • 自己沒有任何做用,在恰當的時候由外部程序解析產生做用。

結合 Retrofit

咱們都記得 Retrofit 的Api 是基於註解的,咱們來看看 Retrofit 是怎麼讀取註解的

上圖這個過程是Retrofit 在讀取 AppApiService接口的這個方法。

/**
 * 獲取首頁圖片
 *
 * @param size 獲取圖片張數
 */
@GET("http://lab.zuimeia.com/wallpaper/category/1/")
Observable<HttpResultV1<ImageData>> getImage(@Query("page_size") int size);複製代碼

咱們來簡單看一下ServiceMethod.Builder 類的構造方法作了些什麼事

public Builder(Retrofit retrofit, Method method) {
  this.retrofit = retrofit;
  this.method = method;
  this.methodAnnotations = method.getAnnotations();//獲取方法上的註解
  this.parameterTypes = method.getGenericParameterTypes();//獲取方法參數列表
  this.parameterAnnotationsArray = method.getParameterAnnotations();//獲取方法參數的二維數組,爲何是二維數組呢,由於一個參數能夠有多個註解呀。
}複製代碼

而後就是 build 方法解析註解了。

public ServiceMethod build() {
    for (Annotation annotation : methodAnnotations) {
    parseMethodAnnotation(annotation);
  }
    int parameterCount = parameterAnnotationsArray.length;
  parameterHandlers = new ParameterHandler<?>[parameterCount];
  for (int p = 0; p < parameterCount; p++) {
    Type parameterType = parameterTypes[p];
    if (Utils.hasUnresolvableType(parameterType)) {
      throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
          parameterType);
    }

    Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
    if (parameterAnnotations == null) {
      throw parameterError(p, "No Retrofit annotation found.");
    }

    parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
  }
}複製代碼

以上就是 build 方法中解析註解的部分代碼,細節就不去仔細看了。具體 Retrofit 是怎麼解析這些參數以及解析的這些參數是幹嗎的不是咱們這裏關心的內容。

自定義註解(重點)

上面咱們學了

  • 使用jdk 反射獲取註解信息
  • 在 Retrofit 中是怎麼獲取註解信息的

這裏我拋出兩個問題:

  • 註解好像僅僅是用於解析自定義註解信息,好像並無什麼卵用啊
  • 你用的最多的自定義標籤有哪些?

在咱們經常使用的 ButterKnife 中,就用了一個BindView 註解,就能夠成功給 View 初始化

@BindView(R.id.tv_welcome)
TextView mTvWelcome;複製代碼

這裏咱們來研究一下到底是怎麼辦到的。

首先咱們來看 BindView 的代碼

@Retention(CLASS) @Target(FIELD)
public @interface BindView {
    /** View ID to which the field will be bound. */
    @IdRes int value();
}複製代碼

源碼中這些信息應該都看得懂吧,前面都講了的,咱們來簡單看一下@IdRes 的返回值限定

@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface IdRes {
}複製代碼

這裏只是一種類型限定,限定了返回類型只能說 Res 的資源。

而BindView的value 方法是沒有 default 值的,而 value 的值又限定死了只能是 int 型的 Res 資源。因此@BindView(R.id.tv_welcome)括號裏面須要給註解賦值。

而後問題來了,前文咱們強調過註解的本質,咱們再來回顧一下:

  • 它就是一個附屬品,依賴於其餘元素存在
  • 自己沒有任何做用,在恰當的時候由外部程序解析產生做用。

那麼被 BindView標註的 TextView mTvWelcome 是怎麼被賦值的呢,咱們來看看ButterKnife.bind() 方法,這個方法通常是在 onCreate 方法裏面調用,傳參通常是用 this。調用了這個方法,而後mTvWelcome字段有了BindView 註解就被賦初始值了。那麼咱們來看看 bind 方法裏面的執行吧。

public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
}複製代碼

這個代碼很簡單,直接跳過了。

這個代碼也簡單,findBindingConstructorForClass方法找到了一個叫LaunchActivity_ViewBinding 的類,而後再調用構造方法。而後,而後就結束了?這個 LaunchActivity_ViewBinding 類特麼是個什麼鬼。。。
而後我在 build 文件裏面找到了這個類~

在這裏,咱們找到了 findViewById 的方法給mTvWelcome作了初始化操做。

而後還剩下一個問題,這個 LaunchActivity_ViewBinding 究竟是從哪裏來的。

這個問題好像有點超綱了,我簡單介紹一下吧,LaunchActivity_ViewBinding 是由APT 生成的,不知道在引入 ButterKnife 的時候你們是否還記得在 gradle 裏面加了一行這樣的代碼apt com.jakewharton:butterknife-compiler:8.4.0.沒錯,這些build 文件裏面的代碼都是 apt 生成的。

APT

APT英文全稱:Android annotation process tool是一種處理註釋的工具,它對源代碼文件進行檢測找出其中的Annotation,使用Annotation進行額外的處理。

Annotation處理器在處理Annotation時能夠根據源文件中的Annotation生成額外的源文件和其它的文件(文件具體內容由Annotation處理器的編寫者決定),APT還會編譯生成源文件和原來的源文件,將它們一塊兒生成class文件。簡言之:APT能夠把註解,在編譯時生成代碼。

ButterKnife 中 apt的大體工做流程就是這樣:代碼編寫完,在編譯的時候掃描java 文件,對包含某些特定註解的 java 文件進行掃描獲取字段及其註解的值,而後使用諸如「javapoet」之類的代碼生成工具生成build 目錄中的***Activity_ViewBinding 文件。

自定義註解實現編譯時檢查代碼

如圖,使用了 NotNull 註解標識的參數,若是傳 null 會報黃色警告。

納尼,註解的本質我都能背得下來了,我背給你看~

它就是一個附屬品,依賴於其餘元素存在
自己沒有任何做用,在恰當的時候由外部程序解析產生做用。

說好的沒有任何做用呢~ 看了@NotNull 的源碼,好像源碼用的幾個關鍵字也沒有讓代碼報黃色警告的意思啊。

因而,不服氣的我把 @NotNull 代碼 copy 出來,寫了一個新的自定義註解 @No。

What the Fuck!!!爲何我本身寫的註解沒有黃色警告。心涼了三分鐘~

好吧,經過上面的測試不得不認可,這個黃色警告跟代碼不要緊,大概是編譯器的功能吧。因而,通過一番 Baidu 和諮詢好友。找到了這個功能~

而後你也能夠修改警告級別。

一樣用於代碼提示的註解還有 @Override(標記重寫父類的方法)、@Deprecated(過時方法)、@SuppressWarnings(忽略警告)。

註解就到這裏吧,高階玩法能夠去研究一下 Retrofit 或者 ButterKnife 的 apt。java Srping 框架的註解玩也玩很溜,對後臺感興趣的能夠去學習。

相關文章
相關標籤/搜索