Java註解解析-運行時註解詳解(RUNTIME)

一 註解的定義

註解(Annotation),也叫元數據。一種代碼級別的說明。它是JDK1.5及以後版本引入的一個特性,與類、接口、枚舉是在同一個層次。它可以聲明在包、類、字段、方法、局部變量、方法參數等的前面,用來對這些元素進行說明 。如果要對於元數據的作用進行分類,還沒有明確的定義,不過我們可以根據它所起的作用,註解不會改變編譯器的編譯方式,也不會改變虛擬機指令執行的順序,它更可以理解爲是一種特殊的註釋,本身不會起到任何作用,需要工具方法或者編譯器本身讀取註解的內容繼而控制進行某種操作。大致可分爲三類:

編寫文檔:通過代碼裏標識的元數據生成文檔。

代碼分析:通過代碼裏標識的元數據對代碼進行分析。

編譯檢查:通過代碼裏標識的元數據讓編譯器能實現基本的編譯檢查。

二 用途

因爲註解可以再代碼編譯期間幫我們完成一些複雜的準備工作,所以我們可以利用註解去完成我們的一些準備工作。比如Greendao,我們註解一個實體類,它要處理成好多邏輯關係類,這些邏輯類讓我們自己去書寫的話那將是一個龐大的代碼量, extends AbstractDao等這些類。比如BufferKnife,我們用註解將控件的屬性傳遞給它,它將生成一些功能類去處理這些值,***_ViewBinding等這些類型的類。運行時註解的使用,比如Retrofit的@GET或者@POST等等都是運行時註解,它的註解的處理必須跟Retrofit的對象有關聯,所以必須定義成運行時的。所以註解已經成爲一種趨勢,比如BufferKnife,EventBus,Darrger,Greendao,Arouter,Retrofit。。。看來我們也要去完成我們的一個註解了。

三 知識準備

Java JDK中包含了三個註解分別爲@Override(校驗格式),@Deprecated:(標記過時的方法或者類),@SuppressWarnnings(註解主要用於抑制編譯器警告)等等java1.8之後有新增了一些註解像@FunctionalInterface()這樣的,對於每個註解的具體使用細節這裏不再論述。我們可以通過點擊這裏來看一下專業解釋! 來看一下@Override的源碼。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)

}

通過源代碼的閱讀我們可以看出生命註解的方式爲@interface,每個註解都需要不少於一個的元註解的修飾,這裏的元註解其實就是修飾註解的註解,可以理解成最小的註解單位吧。。。下面詳細的看下每個註釋註解的意義吧:

@Target:
說明了Annotation所修飾的對象範圍,也就是我們這個註解是用在那個對象上面的:Annotation可被用於 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標。以下屬性是多選狀態,我們可以定義多個註解作用域,比如:

@Target({ElementType.METHOD,ElementType.FIELD}),單個的使用         @Target(ElementType.FIELD)。    
(1.CONSTRUCTOR:構造方法聲明。
(2.FIELD:用於描述域也就是類屬性之類的,字段聲明(包括枚舉常量)。
(3.LOCAL_VARIABLE:用於描述局部變量。
(4.METHOD:用於描述方法。
(5.PACKAGE:包聲明。
(6.PARAMETER:參數聲明。
(7.TYPE:類、接口(包括註釋類型)或枚舉聲明 。
 (8)  .ANNOTATION_TYPE:註釋類型聲明,只能用於註釋註解。

官方解釋:指示註釋類型所適用的程序元素的種類。如果註釋類型聲明中不存在 Target 元註釋,則聲明的類型可以用在任一程序元素上。如果存在這樣的元註釋,則編譯器強制實施指定的使用限制。 例如,此元註釋指示該聲明類型是其自身,即元註釋類型。它只能用在註釋類型聲明上:

    @Target(ElementType.ANNOTATION_TYPE)
    public @interface MetaAnnotationType {

    }

此元註釋指示該聲明類型只可作爲複雜註釋類型聲明中的成員類型使用。它不能直接用於註釋:

   @Target({}) 
   public @interface MemberType {
              ...
  }

這是一個編譯時錯誤,它表明一個 ElementType 常量在 Target 註釋中出現了不只一次。例如,以下元註釋是非法的:

 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
  public @interface Bogus {
              ...
  }

@Retention:
定義了該Annotation被保留的時間長短:某些Annotation僅出現在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因爲Annotation與class在使用上是被分離的)。使用這個meta-Annotation可以對 Annotation的“生命週期”限制。來源於java.lang.annotation.RetentionPolicy的枚舉類型值:
(1).SOURCE:在源文件中有效(即源文件保留)編譯成class文件將捨棄該註解。
(2).CLASS:在class文件中有效(即class保留) 編譯成dex文件將捨棄該註解。
(3).RUNTIME:在運行時有效(即運行時保留) 運行時可見。
也就是說註解處理器能處理這三類的註解,我們通過反射的話只能處理RUNTIME類型的註解.

官方解釋:指示註釋類型的註釋要保留多久。如果註釋類型聲明中不存在 Retention 註釋,則保留策略默認爲 RetentionPolicy.CLASS。只有元註釋類型直接用於註釋時,Target 元註釋纔有效。如果元註釋類型用作另一種註釋類型的成員,則無效。

@Documented:
指示某一類型的註釋將通過 javadoc 和類似的默認工具進行文檔化。應使用此類型來註釋這些類型的聲明:其註釋會影響由其客戶端註釋的元素的使用。如果類型聲明是用 Documented 來註釋的,則其註釋將成爲註釋元素的公共 API 的一部。Documented是一個標記註解,沒有成員。

@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類型被發現,或者到達類繼承結構的頂層。

官方解釋:指示註釋類型被自動繼承。如果在註釋類型聲明中存在 Inherited 元註釋,並且用戶在某一類聲明中查詢該註釋類型,同時該類聲明中沒有此類型的註釋,則將在該類的超類中自動查詢該註釋類型。此過程會重複進行,直到找到此類型的註釋或到達了該類層次結構的頂層 (Object) 爲止。如果沒有超類具有該類型的註釋,則查詢將指示當前類沒有這樣的註釋。
注意,如果使用註釋類型註釋類以外的任何事物,此元註釋類型都是無效的。還要注意,此元註釋僅促成從超類繼承註釋;對已實現接口的註釋無效。

@Repeatable:
Repeatable可重複性,Java 1.8新特性,其實就是把標註的註解放到該元註解所屬的註解容器裏面。可重複性的意思還是用demo來解釋一下吧:

 //定義了一個 註解裏面屬性的返回值是其他註解的數組
      @Target(ElementType.FIELD)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface MyCar {
          MyTag[] value();        ----MyTag  這裏就是MyTag註解的容器。
      }
      //另外一個註解 就是上一個註解返回的註解
      @Target({ElementType.METHOD,ElementType.FIELD})
      @Retention(RetentionPolicy.CLASS)
      @Repeatable(MyCar.class)    --------這裏添加這個屬性之後 我們的這個註解就可以重複的添加到我們定義的容器中了,注意裏面的值時  我們定義的容器註解的class對象.
      public @interface MyTag {       ........MyTag

          String name () default "" ;

          int size () default 0 ;
      }
      //使用
          @MyTag(name = "BWM", size = 100)
          @MyTag()
          public Car car;
           //如果我們的註解沒有@Repeatable的話,這樣寫的話是報錯的,加上之後就是這樣的了  

這個註解是很特殊的,我們的註解中有@Repeatable(MyCar.class)這樣的元註解的話,就是說當前標註的註解(MyTag註解)放到我們的值(MyCar.class)這個註解容器裏面。那麼我們再處理註解的時候獲取到的是我們最後的註解容器(MyCar註解),這樣說有點生硬下面看demo:

 使用:
      public class HomeActivity extends AppCompatActivity {
          @MyTag(name = "BWM", size = 100)
          @MyTag(name = "大衆"  ,size = 200)  ......這裏用了它的重複性.
          Car car;
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_home);
              AnnotationProccessor.instance().inject(this);   //這裏去處理註解
            //  Log.e("WANG", "HomeActivity.onCreate." + car.toString());
          }
      }
      處理過程:
      Class<?> aClass = o.getClass();
              Field[] declaredFields = aClass.getDeclaredFields();
              for (Field field:declaredFields) {
                  if(field.getName().equals("car")){
                      Annotation[] annotations = field.getAnnotations();
                      for (int i = 0; i <annotations.length; i++) {
                          Annotation annotation = annotations[i];
                          //我們獲取的該字段上面的註解只有一個  那就是 MyCar註解,看結果1的打印.
                          //但是我們明明標註的是 MyTag. 爲什麼獲取的是註解容器呢.
                          //這就是@Repeatable的強大之處.

                          Class<? extends Annotation> aClass1 = annotation.annotationType();
                          Log.e("WANG","AnnotationProccessor.MyCar"+aClass1 );
                      }
                      MyCar annotation = field.getAnnotation(MyCar.class);
                      MyTag[] value = annotation.value();
                      for (int i = 0; i <value.length; i++) {
                          MyTag myTag = value[i];
                          Log.e("WANG","AnnotationProccessor.MyTag   name  value   is  "+myTag.name() );
                      }
                  }      

      結果是:
      AnnotationProccessor.MyCarinterface cn.example.wang.routerdemo.annotation.MyCar.1
      AnnotationProccessor.MyTag   name  value   is  BWM.2
      AnnotationProccessor.MyTag   name  value   is  大衆.3

三 自定義運行時註解

通過以上的學習我們知道@interface是聲明註解的關鍵字,每個註解需要註明生命週期以及作用範圍.你可以給註解定義值.也就是再註解內部定義我們需要的方法.這樣註解就可以再自己的生命週期內爲我們做事.這裏我們就自定義一個爲一個對象屬性初始化的註解吧,類似於Dagger的功能。

public @interface MyTag {

}

註解裏面的定義也是有規定的:

  • 註解方法不能帶有參數。
  • 註解方法返回值類型限定爲:基本類型、String、Enums、Annotation或者這些類型的數組。
  • 註解方法可以有默認值。
  • 註解本身能夠包含元註解,元註解被用來註解其他註解。

我們就來試一下吧!

public @interface MyTag {
  //聲明返回值類型,這裏可沒有大括號啊,可以設置默認返回值,然後就直接";"了啊。
    String name () default "" ;
    int size () default 0 ;
}

定義好了註解我們就來規定我們自定義的註解要在哪裏用?要何時用?因爲我們這裏使用了反射來處理註解,反射就是在代碼的運行的時候通過class對象反相的去獲取類內部的東西,不熟悉反射機制的請移步這裏Android開發者必須瞭解的反射基礎,所以我們定義該註解的生命週期在運行時,並且該註解的的目的是爲自定義屬性賦值,那麼我們的作用域就是FIELD。這裏面定義了我們要初始化的bean的基本屬性,給了默認值。這樣我們就可以用該註解去創建我們需要的bean對象。

@Target(ElementType.FIELD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTag {
    String name () default "" ;
    int size () default 0 ;
}

好了接下來看怎麼使用我們的這個自定義的註解!

public class HomeActivity extends AppCompatActivity {
    @MyTag(name = "BMW",size = 100)
    Car car;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        //這裏我們要首先註冊一下這個類
        AnnotationCar.instance().inject(this);
      //當程序運行的時候這裏將會輸出該類Car的屬性值。
        Log.e("WANG","Car is "+car.toString());
    }
}

註解如果沒有註解處理器,那麼該註解將毫無意義。這裏呢我們在這個Activity裏面定義了一個Car類的屬性,然後再car這個變量上面定義我們的註解,並且給我們的註解賦值。然後我們再onCreate方法裏面初始化我們的註解處理器。然後運行代碼,log日誌將打印Car類的信息,先來看下結果吧

cn.example.wang.routerdemo E/WANG: Car is Car [name=BMW, size=100]

這樣我們的自定義註解就有作用了,下面是”註解處理器“的代碼,這裏都是我們自己編寫的處理註解的代碼,其實系統是自帶註解處理器的,不過它一般用來處理源碼註釋和編譯時註釋。

//自定義的類
/**
 * Created by WANG on 17/11/21.
 */
public class AnnotationCar {
    private static AnnotationCar annotationCar;
    public static AnnotationCar instance(){
        synchronized (AnnotationCar.class){
            if(annotationCar == null){
                annotationCar = new AnnotationCar();
            }
            return annotationCar;
        }
    }

    public void inject(Object o){
        Class<?> aClass = o.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field field:declaredFields) {
            if(field.getName().equals("car") && field.isAnnotationPresent(MyTag.class)) {
                MyTag annotation = field.getAnnotation(MyTag.class);
                Class<?> type = field.getType();
                if(Car.class.equals(type)) {
                    try {
                        field.setAccessible(true);
                        field.set(o, new Car(annotation.name(), annotation.size()));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

這就說明了爲什麼註解和反射是同時進入我們的知識圈裏面的吧!這裏呢我們先獲取到類裏面所有的屬性,然後去找到被我們的註解MyTag修飾的那個屬性,然後找到之後,先取我們註解裏面的值,然後賦值給我們類裏面的屬性!這樣我們就用註解去初始化了一個屬性值。就是這麼簡單!

四 總結

運行時註解是我們比較好理解的,知道反射和註解基礎之後就可以寫出來個小demo了。但是運行時註解是是我們最不常用的註解,因爲反射再運行時的操作是十分的耗時的,我們不會因爲一些代碼的簡潔而影響app的性能。所以呢運行時註解只是大家認識註解的一個入口,接下來我將陸續的介紹註解的通用寫法,CLASS註解和SOURCE註解,讓我們來完成屬於自己的BufferKnife!

下一章節將詳細介紹,CLASS註解和SOURCE註解,讓我們來完成屬於自己的BufferKnife!

歡迎大家評論區留言指出文章錯誤~ 謝謝各位看官!
歡迎大家關注!
我的掘金
我的CSDN
我的簡書
Github

Demo地址,歡迎start

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章