Java註解解析-基礎+運行時註解(RUNTIME)

寫在前面 :

該文章介紹了註解的一些基礎,我相信不少人對註解的基礎已經瞭如執掌,不妨接着往下看。讀完該章節你能清楚的認識到一個註解的組成部分以及如何寫出想要的註解。該章介紹了咱們熟悉的運行時註解的處理方法,由於Retrofit都用了。文末有下章節預告:有史以來最全面解析CLASS註解,帶你實現本身的BufferKnufe(包看包會)。java

一 註解的定義

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

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

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

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

二 用途

由於註解運行在單獨的JVM裏面,因此咱們可使用JVM提供給咱們的任何依賴。另外CLASSSOURCE類型的註解是再編譯期間完成對註解的處理,因此能夠再代碼編譯期間幫咱們完成一些複雜的準備工做。就拿BufferKnife來講,再處理註解的期間生成咱們註解對象相關的***_ViewBinding等類來處理Viewbash

三 知識準備

Java JDK中包含了三個註解分別爲@Override(校驗格式),@Deprecated(標記過期的方法或者類),@SuppressWarnnings(註解主要用於抑制編譯器警告),對於每一個註解的具體使用細節這裏再也不論述。咱們能夠經過點擊這裏來看一下專業解釋! 來看一下@Override的源碼。app

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

經過源代碼的閱讀咱們能夠看出生命註解的方式爲@interface,每一個註解都須要很多於一個的元註解的修飾,這裏的元註解其實就是修飾註解的註解,能夠理解成最小的註解單位吧。。。下面詳細的看下每一個註釋註解的意義吧:ide

  • @Target
    說明了註解所修飾的對象範圍,也就是咱們這個註解是用在那個對象上面的:註解可被用於 packagestypes(類、接口、枚舉、註解類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。在註解類型的聲明中使用了target可更加明晰其修飾的目標。如下屬性是多選狀態,咱們能夠定義多個註解做用域,好比: (1).CONSTRUCTOR:用於描述構造器。
    (2).FIELD:用於描述域也就是類屬性之類的。
    (3).LOCAL_VARIABLE:用於描述局部變量。
    (4).METHOD:用於描述方法。
    (5).PACKAGE:用於描述包。
    (6).PARAMETER:用於描述參數。
    (7).TYPE:用於描述類、接口(包括註解類型) 或enum聲明。
@Target({ElementType.METHOD,ElementType.FIELD}),單個的使用@Target(ElementType.FIELD) 。
複製代碼
  • @Retention
    定義了該註解被保留的時間長短:某些註解僅出如今源代碼中,而被編譯器丟棄;而另外一些卻被編譯在class文件中;編譯在class文件中的註解可能會被虛擬機忽略,而另外一些在class被裝載時將被讀取(請注意並不影響class的執行,由於註解與class在使用上是被分離的)。使用這個meta-Annotation能夠對 註解的「生命週期」限制。也就是說註解處理器能處理這三類的註解,咱們經過反射的話只能處理RUNTIME類型的註解.來源於java.lang.annotation.RetentionPolicy的枚舉類型值:
    (1).SOURCE:在源文件中有效(即源文件保留)編譯成class文件將捨棄該註解。
    (2).CLASS:在class文件中有效(即class保留) 編譯成dex文件將捨棄該註解。
    (3).RUNTIME:在運行時有效(即運行時保留) 運行時可見。工具

  • @Documented
    用於描述其它類型的註解應該被做爲被標註的程序成員的公共API,所以能夠被例如javadoc此類的工具文檔化。Documented是一個標記註解,沒有成員。post

  • @Inherited
    元註解是一個標記註解,@Inherited闡述了某個被標註的類型是被繼承的。若是一個使用了@Inherited修飾的註解類型被用於一個class,則這個註解將被用於該class的子類。 注意:@Inherited 註解類型是被標註過的class的子類所繼承。類並不從它所實現的接口繼承註解,方法並不從它所重載的方法繼承註解。當被@Inherited類型標註的註解的Retention值爲RetentionPolicy.RUNTIME,則反射API加強了這種繼承性。若是咱們使用java.lang.reflect去查詢一個@Inherited類型的註解時,反射代碼檢查將展開工做:檢查class和其父類,直到發現指定的註解類型被發現,或者到達類繼承結構的頂層。

  • @Repeatable
    Repeatable可重複性,Java1.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註解全面解析,包看包會之完成屬於本身的BufferKnife!

歡迎你們評論區留言指出文章錯誤~ 謝謝各位看官! 歡迎你們持續關注!

個人掘金
個人CSDN
個人簡書
Github Demo地址,歡迎start

相關文章
相關標籤/搜索