Java注解完全解析——運(yùn)行時(shí)注解(RUNTIME)

一 注解的定義

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

編寫文檔:通過代碼里標(biāo)識的元數(shù)據(jù)生成文檔。

代碼分析:通過代碼里標(biāo)識的元數(shù)據(jù)對代碼進(jìn)行分析。

編譯檢查:通過代碼里標(biāo)識的元數(shù)據(jù)讓編譯器能實(shí)現(xiàn)基本的編譯檢查。

二 用途

因?yàn)樽⒔饪梢栽俅a編譯期間幫我們完成一些復(fù)雜的準(zhǔn)備工作,所以我們可以利用注解去完成我們的一些準(zhǔn)備工作。比如Greendao,我們注解一個(gè)實(shí)體類,它要處理成好多邏輯關(guān)系類,這些邏輯類讓我們自己去書寫的話那將是一個(gè)龐大的代碼量, extends AbstractDao等這些類。比如BufferKnife,我們用注解將控件的屬性傳遞給它,它將生成一些功能類去處理這些值,***_ViewBinding等這些類型的類。運(yùn)行時(shí)注解的使用,比如Retrofit的@GET或者@POST等等都是運(yùn)行時(shí)注解,它的注解的處理必須跟Retrofit的對象有關(guān)聯(lián),所以必須定義成運(yùn)行時(shí)的。所以注解已經(jīng)成為一種趨勢,比如BufferKnife,EventBus,Darrger,Greendao,Arouter,Retrofit。。。看來我們也要去完成我們的一個(gè)注解了。

三 知識準(zhǔn)備

Java JDK中包含了三個(gè)注解分別為@Override(校驗(yàn)格式),@Deprecated(標(biāo)記過時(shí)的方法或者類),@SuppressWarnnings(注解主要用于抑制編譯器警告),對于每個(gè)注解的具體使用細(xì)節(jié)這里不再論述。我們可以通過點(diǎn)擊這里來看一下專業(yè)解釋! 來看一下@Override的源碼。

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

通過源代碼的閱讀我們可以看出生命注解的方式為@interface,每個(gè)注解都需要不少于一個(gè)的元注解的修飾,這里的元注解其實(shí)就是修飾注解的注解,可以理解成最小的注解單位吧。。。下面詳細(xì)的看下每個(gè)注釋注解的意義吧:

  • @Target :說明了Annotation所修飾的對象范圍,也就是我們這個(gè)注解是用在那個(gè)對象上面的:Annotation可被用于 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構(gòu)造方法、成員變量、枚舉值)、方法參數(shù)和本地變量(如循環(huán)變量、catch參數(shù))。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標(biāo)。以下屬性是多選狀態(tài),我們可以定義多個(gè)注解作用域,比如:
    (1).CONSTRUCTOR:用于描述構(gòu)造器。
    (2).FIELD:用于描述域也就是類屬性之類的。
    (3).LOCAL_VARIABLE:用于描述局部變量。
    (4).METHOD:用于描述方法。
    (5).PACKAGE:用于描述包。
    (6).PARAMETER:用于描述參數(shù)。
    (7).TYPE:用于描述類、接口(包括注解類型) 或enum聲明。
@Target({ElementType.METHOD,ElementType.FIELD}),單個(gè)的使用@Target(ElementType.FIELD) 。
  • @Retention:定義了該Annotation被保留的時(shí)間長短:某些Annotation僅出現(xiàn)在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會(huì)被虛擬機(jī)忽略,而另一些在class被裝載時(shí)將被讀取(請注意并不影響class的執(zhí)行,因?yàn)锳nnotation與class在使用上是被分離的)。使用這個(gè)meta-Annotation可以對 Annotation的“生命周期”限制。來源于java.lang.annotation.RetentionPolicy的枚舉類型值:
    (1).SOURCE:在源文件中有效(即源文件保留)編譯成class文件將舍棄該注解。
    (2).CLASS:在class文件中有效(即class保留) 編譯成dex文件將舍棄該注解。
    (3).RUNTIME:在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留) 運(yùn)行時(shí)可見。

也就是說注解處理器能處理這三類的注解,我們通過反射的話只能處理RUNTIME類型的注解.

  • @Documented:用于描述其它類型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。Documented是一個(gè)標(biāo)記注解,沒有成員。

  • @Inherited:元注解是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類型是被繼承的。如果一個(gè)使用了@Inherited修飾的annotation類型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類。 注意:@Inherited annotation類型是被標(biāo)注過的class的子類所繼承。類并不從它所實(shí)現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation。當(dāng)@Inherited annotation類型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強(qiáng)了這種繼承性。如果我們使用java.lang.reflect去查詢一個(gè)@Inherited annotation類型的annotation時(shí),反射代碼檢查將展開工作:檢查class和其父類,直到發(fā)現(xiàn)指定的annotation類型被發(fā)現(xiàn),或者到達(dá)類繼承結(jié)構(gòu)的頂層。

  • @Repeatable
    Repeatable可重復(fù)性,Java 1.8新特性,其實(shí)就是把標(biāo)注的注解放到該元注解所屬的注解容器里面。可重復(fù)性的意思還是用demo來解釋一下吧:

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

    String name () default "" ;

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

這個(gè)注解是很特殊的,我們的注解中有@Repeatable(MyCar.class)這樣的元注解的話,就是說當(dāng)前標(biāo)注的注解(MyTag注解)放到我們的值(MyCar.class)這個(gè)注解容器里面。那么我們再處理注解的時(shí)候獲取到的是我們最后的注解容器(MyCar注解),這樣說有點(diǎn)生硬下面看demo:

使用:
public class HomeActivity extends AppCompatActivity {
    @MyTag(name = "BWM", size = 100)
    @MyTag(name = "大眾"  ,size = 200)  ......這里用了它的重復(fù)性.
    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];
                    //我們獲取的該字段上面的注解只有一個(gè)  那就是 MyCar注解,看結(jié)果1的打印.
                    //但是我們明明標(biāo)注的是 MyTag. 為什么獲取的是注解容器呢.
                    //這就是@Repeatable的強(qiáng)大之處.
         
                    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() );
                }
            }      
            
結(jié)果是:
AnnotationProccessor.MyCarinterface cn.example.wang.routerdemo.annotation.MyCar.1
AnnotationProccessor.MyTag   name  value   is  BWM.2
AnnotationProccessor.MyTag   name  value   is  大眾.3

三 自定義運(yùn)行時(shí)注解

通過以上的學(xué)習(xí)我們知道@interface是聲明注解的關(guān)鍵字,每個(gè)注解需要注明生命周期以及作用范圍.你可以給注解定義值.也就是再注解內(nèi)部定義我們需要的方法.這樣注解就可以再自己的生命周期內(nèi)為我們做事.這里我們就自定義一個(gè)為一個(gè)對象屬性初始化的注解吧,類似于Dagger的功能。

public @interface MyTag {

}

注解里面的定義也是有規(guī)定的:

  • 注解方法不能帶有參數(shù)。

  • 注解方法返回值類型限定為:基本類型、String、Enums、Annotation或者這些類型的數(shù)組。

  • 注解方法可以有默認(rèn)值。

  • 注解本身能夠包含元注解,元注解被用來注解其他注解。

我們就來試一下吧!

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

定義好了注解我們就來規(guī)定我們自定義的注解要在哪里用?要何時(shí)用?因?yàn)槲覀冞@里使用了反射來處理注解,反射就是在代碼的運(yùn)行的時(shí)候通過class對象反相的去獲取類內(nèi)部的東西,不熟悉反射機(jī)制的請移步這里Android開發(fā)者必須了解的反射基礎(chǔ),所以我們定義該注解的生命周期在運(yùn)行時(shí),并且該注解的的目的是為自定義屬性賦值,那么我們的作用域就是FIELD。這里面定義了我們要初始化的bean的基本屬性,給了默認(rèn)值。這樣我們就可以用該注解去創(chuàng)建我們需要的bean對象。

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

好了接下來看怎么使用我們的這個(gè)自定義的注解!

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);
 //這里我們要首先注冊一下這個(gè)類
 AnnotationCar.instance().inject(this);
 //當(dāng)程序運(yùn)行的時(shí)候這里將會(huì)輸出該類Car的屬性值。
 Log.e("WANG","Car is "+car.toString());
 }
}

注解如果沒有注解處理器,那么該注解將毫無意義。這里呢我們在這個(gè)Activity里面定義了一個(gè)Car類的屬性,然后再car這個(gè)變量上面定義我們的注解,并且給我們的注解賦值。然后我們再onCreate方法里面初始化我們的注解處理器。然后運(yùn)行代碼,log日志將打印Car類的信息,先來看下結(jié)果吧!

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

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

//自定義的類
/**
 * 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();
                    }
                }
            }
        }
    }
}

這就說明了為什么注解和反射是同時(shí)進(jìn)入我們的知識圈里面的吧!這里呢我們先獲取到類里面所有的屬性,然后去找到被我們的注解MyTag修飾的那個(gè)屬性,然后找到之后,先取我們注解里面的值,然后賦值給我們類里面的屬性!這樣我們就用注解去初始化了一個(gè)屬性值。就是這么簡單!

四 總結(jié)

運(yùn)行時(shí)注解是我們比較好理解的,知道反射和注解基礎(chǔ)之后就可以寫出來個(gè)小demo了。但是運(yùn)行時(shí)注解是是我們最不常用的注解,因?yàn)榉瓷湓龠\(yùn)行時(shí)的操作是十分的耗時(shí)的,我們不會(huì)因?yàn)橐恍┐a的簡潔而影響app的性能。所以呢運(yùn)行時(shí)注解只是大家認(rèn)識注解的一個(gè)入口。接下來我將陸續(xù)的介紹注解的通用寫法。

下一章節(jié)將詳細(xì)介紹,CLASS注解和SOURCE注解,讓我們來完成屬于自己的BufferKnife!

歡迎大家評論區(qū)留言指出文章錯(cuò)誤~ 謝謝各位看官!
歡迎大家關(guān)注!
我的掘金
我的CSDN
我的簡書
Github

Demo地址,歡迎start

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內(nèi)容

  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一種元程序中的元素關(guān)聯(lián)任何信息和...
    九尾喵的薛定諤閱讀 3,204評論 0 2
  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來 What:A...
    zlcook閱讀 29,223評論 15 116
  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful 2、Retrofit解析...
    隔壁老李頭閱讀 6,625評論 4 31
  • 三千刀子在酒杯中搖晃, 你渴望蜻蜓點(diǎn)水, 她從你生命中飛過! 愛的濃蔭只剩一座廢城, 月光照了進(jìn)來, 眼淚流不出去!
    簡村小吹閱讀 229評論 1 5
  • 1-心理咨詢與治療的三大流派分別是精神分析、人本主義和認(rèn)知行為。 羅杰斯和馬斯洛是人本主義流派的兩位大師級人物,羅...
    依諾芝閱讀 410評論 0 0