Retrofit解析4之注解

整體Retrofit內(nèi)容如下:

由于Retrofit里面大量的用到了注解赵颅,為了讓大家更好的學(xué)習(xí)Retrofit,特意準(zhǔn)備了一篇Java注解暂刘,如果大家已經(jīng)對(duì)Java注解已經(jīng)很熟悉了饺谬,就略過(guò),看下一篇文章
本篇文章主要講解

  • 1谣拣、Java 注解技術(shù)基本概念
  • 2募寨、Java 元注解
  • 3、標(biāo)準(zhǔn)注解/內(nèi)建注解
  • 4森缠、自定義注解
  • 5拔鹰、注解處理器
  • 6、注解思維導(dǎo)圖
  • 7贵涵、注解原理

一列肢、Java注解技術(shù)基本概念

(一) 什么是注解

Annotation是java 5開(kāi)始引入的新特征。中文名稱一般叫注解宾茂。它提供了一種安全的類(lèi)似于注釋的機(jī)制瓷马,用來(lái)將任何的信息或元數(shù)據(jù)(metadata)與程序元素(類(lèi)、方法跨晴、成員變量等)進(jìn)行關(guān)聯(lián)欧聘。

上面的類(lèi)似官方的解釋?zhuān)俏覀冊(cè)賮?lái)通俗的解釋一下:
我們都知道在Java代碼中使用注解是為了提升代碼的可讀性,也就是說(shuō)坟奥,注釋是給人看的(對(duì)編譯器來(lái)說(shuō)是沒(méi)有意義上的)树瞭。注解可以看做注釋的"加強(qiáng)升級(jí)版"拇厢,它可以向編譯器、虛擬機(jī)等解釋說(shuō)明一些事情(也就是說(shuō)它對(duì)編譯器等工具也是"可讀"的)晒喷。比如我們非常熟悉的@Overrider 注解孝偎,它的作用是告訴編譯器它所注解的方法是重寫(xiě)父類(lèi)中的方法,這樣編譯器就會(huì)檢查父類(lèi)是否存在這個(gè)方法凉敲,以及這個(gè)方法的簽名與父類(lèi)是否相同衣盾。
也就是說(shuō),注解是描述Java代碼的代碼爷抓,它能夠被編譯器解析势决,注解處理工具在運(yùn)行時(shí)也能夠解釋注解。除了向編譯器等傳遞一些信息蓝撇,我們也可以用注解生成代碼果复。比如我們可以用注解描述我們的意圖,然后讓注解解析工具來(lái)解析注解渤昌,以此來(lái)生成一些"模板化"的代碼虽抄。注解是一種"被動(dòng)"的信息,必須有編譯器或虛擬機(jī)來(lái)"主動(dòng)"解析它独柑,它才能發(fā)揮自己的作用迈窟。

(二) 什么是元數(shù)據(jù)(metadata)

元數(shù)據(jù)由metadata翻譯來(lái)的,所謂元數(shù)據(jù)就是"關(guān)于數(shù)據(jù)的數(shù)據(jù)"忌栅,更通俗的說(shuō)就是描述數(shù)據(jù)的數(shù)據(jù)的车酣,對(duì)數(shù)據(jù)及信息資源的描述性信息,比如一個(gè)文本文件索绪,有創(chuàng)建時(shí)間湖员、創(chuàng)建人、文件大小等數(shù)據(jù)者春,都是可以理解為是元數(shù)據(jù)破衔。在java中,元數(shù)據(jù)以標(biāo)簽的形式存在java代碼中钱烟,它的存在并不影響程序代碼的編譯和執(zhí)行晰筛,通常它被用來(lái)生成其他的文件或運(yùn)行時(shí)知道被運(yùn)行代碼的描述信息。java代碼中的javadoc和注解都屬于元數(shù)據(jù)拴袭。

(三) 注解的前世今生

注解首先在第三版的Java Language Specification中被提出读第,并在Java 5中被實(shí)現(xiàn)。

(四)為什么要使用注解

  • 1拥刻、在未使用Annotation之前(甚至是使用之后)绑谣,一般使用XML來(lái)應(yīng)用于元數(shù)據(jù)的描述监婶。不是何時(shí)開(kāi)始一些開(kāi)發(fā)人員和架構(gòu)師發(fā)現(xiàn)XML的維護(hù)原來(lái)越復(fù)雜和糟糕凶掰,他們希望使用一些和代碼緊密耦合的東西,而不是像XML一樣是松耦合的(在某些情況下甚至是完全分離的)代碼描述惠窄。如果你在百度或者google中搜索"xml vs annotations",就會(huì)看到關(guān)于這個(gè)話題的辯論漾橙。因?yàn)閄ML的配置就是為了分離代碼和配置而設(shè)置的杆融。但是用Annotation(注解)還是XML各有利弊。(下面有舉例說(shuō)明)
  • 2霜运、另外一個(gè)很重要的因素是Annotation注解定義一種標(biāo)準(zhǔn)的描述元數(shù)據(jù)的方式脾歇。在這之前,開(kāi)發(fā)者通常使用他們自己的方式定義元數(shù)據(jù)淘捡。例如藕各,使用標(biāo)記interface,注釋?zhuān)瑃ransient關(guān)鍵字等焦除。每個(gè)程序員都用自己的方式定義元數(shù)據(jù)激况,而不像Annotation這種標(biāo)準(zhǔn)的方式。

下面我簡(jiǎn)單舉例說(shuō)明踢京。
比如誉碴,你想為你的應(yīng)用設(shè)置很多常量或參數(shù)宦棺,這種情況下瓣距,XML是一個(gè)很好的選擇,因?yàn)樗粫?huì)與特定的代碼關(guān)聯(lián)代咸。如果你想把某個(gè)方法聲明為服務(wù)蹈丸,那么使用Annotation會(huì)更好一些,因?yàn)檫@種情況下需要注解和方法高度耦合一起呐芥。

因?yàn)閄ML是松耦合的逻杖,注解是緊耦合的,所以目前主流的框架將XML和Annotation兩種方式結(jié)合使用思瘟,平衡兩者之前的利弊荸百。
在需要高度耦合的地方,Annotation注解比XML更容易維護(hù)滨攻,閱讀更方便
在需要松耦合的地方够话,使用XML更方便
在某個(gè)方法聲明為服務(wù)時(shí),這種緊耦合的情況下光绕,比較適合Annation注解女嘲。

(五)、注解的作用

Annotation 注解 通常被用以作以下目的:

  • 1诞帐、編譯器指令
  • 2欣尼、構(gòu)建時(shí)指令
  • 3、運(yùn)行時(shí)指令
    Java 內(nèi)置了三種編譯器指令停蕉,Java注解可以應(yīng)用于構(gòu)建時(shí)愕鼓,即當(dāng)你構(gòu)建你的項(xiàng)目時(shí)钙态,構(gòu)建的過(guò)程包括產(chǎn)生源代碼、編譯源代碼菇晃、產(chǎn)生xml文件驯绎,將編譯過(guò)的代碼或者文件打包進(jìn)jar文件等。通常情況下谋旦,注解不會(huì)出現(xiàn)在編譯之后的Java代碼中剩失,但是想要出現(xiàn)也是可以的。Java支持運(yùn)行時(shí)注解册着。這些注解可以通過(guò)java反射訪問(wèn)拴孤,運(yùn)行時(shí)注解主要是提供給程序或者第三方API一些指令。

(六) 注解基礎(chǔ)

一個(gè)簡(jiǎn)單的Java注解 類(lèi)似于下面的這種 @Doctor ,"@" 符號(hào)告訴編譯器這是一個(gè)注解甲捏,跟在"@" 符號(hào)后面的是注解的名字演熟,上述的例子中注解的名字是Doctor。

(七) 注解元素

Java 注解可以使用元素設(shè)置一些值司顿,元素類(lèi)似于屬性或者參數(shù)芒粹。下面是一個(gè)包含元素注解的例子

@Doctor (name = "張三")

上述注解的元素名稱是name,值是"張三",沒(méi)有元素的注解不需要括號(hào)大溜。注解可以包含多個(gè)元素化漆,下面就是包含多個(gè)元素的例子

@Doctor(name = "張三", sex= "男")

當(dāng)注解只包含一個(gè)元素時(shí),你可以省去寫(xiě)元素的名字钦奋,直接賦值即可座云。下面的例子就是直接賦值。

@InsertNew("yes")

(八)注解使用

Annotation 注解可以在以下場(chǎng)合被使用到

  • 類(lèi)
  • 接口
  • 方法
  • 方法參數(shù)
  • 屬性
  • 局部變量

二付材、元注解

(一) 什么是元注解

元注解朦拖,元注解就是負(fù)責(zé)注解其它注解.Java5.0定義了4個(gè)標(biāo)準(zhǔn)的meta-annotation類(lèi)型,它們唄用來(lái)提供對(duì)其他annotation類(lèi)型作說(shuō)明厌衔。Java5.0定義的元注解:

  • @Target
  • @Retention
  • @Documented
  • @Inherited

這些類(lèi)型和它們所支持的類(lèi)在java.lang.annotation包中可以找到璧帝。下面我們來(lái)看一下每一個(gè)元注解的作用和說(shuō)明

1、@Target

表示該注解可以用在什么地方富寿,由ElementType枚舉定義

  • CONSTRUCTOR:構(gòu)造器的聲明
  • FIELD:域聲明(包括enum實(shí)例)
  • LOCAL_VARIABLE:布局變量聲明
  • METHOD:方法聲明
  • PACKAGE:包聲明
  • PARAMETER:參數(shù)聲明
  • TYPE:類(lèi)睬隶、接口(包括注解類(lèi)型)或enum聲明
  • ANNOTATION_TYPE:注解聲明(應(yīng)用于另一個(gè)注解上)
  • TYPE_PARAMETER:類(lèi)型參數(shù)聲明(1.8新加入)
  • TYPE_USE:類(lèi)型使用聲明(1.8新加入)
    PS: 當(dāng)注解未制定Target值時(shí),此注解可以使用任何元素之上作喘,就是上面的類(lèi)型理疙。

舉例如下:

@Target(ElementType.METHOD)
public @interface MethodInfo { 
}

上面代碼中我們使用"@Target"元注解來(lái)說(shuō)明MethodInfo這個(gè)注解只能應(yīng)用于對(duì)方法進(jìn)行注解。

2泞坦、@Retention

表示需要在什么級(jí)別保存該注解信息窖贤,由RetentionPolicy枚舉定義

  • SOURCE:注解將編譯器丟棄(該類(lèi)型的注解信息只會(huì)保留在源碼里,源碼經(jīng)過(guò)編譯后,注解信息會(huì)被丟棄赃梧,不會(huì)保留在編譯好的class文件里)
  • CLASS:注解在class中可用滤蝠,但會(huì)被VM丟棄(該類(lèi)型的注解信息會(huì)保留在源碼里和class文件里,在執(zhí)行的時(shí)候授嘀,不會(huì)加載到虛擬機(jī)中(JVM)中)
  • RUNTIME:VM將在運(yùn)行期也保留注解信息物咳,因此可以通過(guò)反射機(jī)制讀取注解信息(源碼、class文件和執(zhí)行的時(shí)候都有注解的信息)

PS:當(dāng)胡姐未定義Retention值時(shí)蹄皱,默認(rèn)值是CLASS

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

這表明@Override 注解只在源碼階段存在览闰,javac在編譯過(guò)程中去掉該注解。

3巷折、@Documented

表示注解會(huì)被包含在javaapi文檔中
當(dāng)一個(gè)注解被@Documented元注解所修飾時(shí)压鉴,那么無(wú)論在哪里使用這個(gè)注解,都會(huì)被Javadoc工具文檔化锻拘。
舉例如下

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)public @interface Documented {
}

這個(gè)元注解唄@Documented修飾油吭,表示它本身會(huì)被文檔化。@Retention注解的值RetentionPolicy.RUNTIME表示@Documented這個(gè)注解能保留在運(yùn)行時(shí)署拟;@Target元注解的值ElementType.ANNOTATION_TYPE表示@Documented這個(gè)注解只能夠來(lái)修飾注解類(lèi)型

4婉宰、@Inherited

允許子類(lèi)繼承父類(lèi)的注解。
用于描述某個(gè)被標(biāo)注的類(lèi)型可被繼承的推穷,如果一個(gè)使用了@Inherited修飾的annotation類(lèi)型類(lèi)型被用于一個(gè)class心包,則這個(gè)annotation將被用于該class類(lèi)的子類(lèi)。
表明被修飾的注解類(lèi)型是自動(dòng)繼承的缨恒。如果你想讓一個(gè)類(lèi)和它的子類(lèi)都包含某個(gè)注解谴咸,就可以使用@Inherited來(lái)修飾這個(gè)注解。也就是說(shuō)骗露,假設(shè)@Parent類(lèi)是Child類(lèi)的父類(lèi),那么我們?nèi)粲帽籃Inherited元注解所修飾的某個(gè)注解對(duì)Parent類(lèi)進(jìn)行了修飾血巍,則相當(dāng)于Child類(lèi)也被該注解所修飾了萧锉。這個(gè)元注解的定義如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)public @interface Inherited {
}
@Inherited
public @interface MyAnnotation {
}

@MyAnnotation
public class MySuperClass {
}

public class MySubClass extends MySuperClass {
}

上述代碼的大致意思是使用@Inherited修飾注解MyAnnotation使用MyAnnotation注解MySuperClass實(shí)現(xiàn)類(lèi)MySubclass繼承自MySuperClass

當(dāng)@Inherited annotation類(lèi)型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強(qiáng)了這種繼承性述寡。如果我們使用java.lang.reflect去查詢一個(gè)@Inherited annotation類(lèi)型的annotation時(shí)柿隙,反射代碼檢查將展開(kāi)工作:檢查class和其父類(lèi),直到發(fā)現(xiàn)指定的annotation類(lèi)型被發(fā)現(xiàn)鲫凶,或者到達(dá)類(lèi)繼承結(jié)構(gòu)的頂層禀崖。

三、標(biāo)準(zhǔn)注解/內(nèi)建注解

Java本身內(nèi)建了一些注解螟炫,用來(lái)為編譯器提供指令波附。如下:

  • @Override
  • @Deprecated
  • @SuppressWarnings
    下面讓我們?cè)敿?xì)了解下三個(gè)標(biāo)準(zhǔn)注解/內(nèi)建注解

1、@Override注解

@Override注解用來(lái)修飾對(duì)父類(lèi)進(jìn)行重寫(xiě)的方法。如果一個(gè)并非重寫(xiě)父類(lèi)的方法使用這個(gè)注解掸屡,編譯器將提示錯(cuò)誤封寞。

實(shí)際上在子類(lèi)中重寫(xiě)父類(lèi)或接口的方法,@Overrider并不是必須的仅财。但是還是建議使用這個(gè)注解狈究,在某些情況下,假設(shè)你修改了父類(lèi)的方法的名字盏求,那么之前重寫(xiě)子類(lèi)方法將不再屬于重寫(xiě)抖锥,如果沒(méi)有@Override,你將不會(huì)覺(jué)察到這個(gè)子類(lèi)的方法碎罚。有了這個(gè)注解修飾宁改,編譯器則會(huì)提示你這些信息。
例子如下:

public class MySuperClass {

    public void doTheThing() {
        System.out.println("Do the thing");
    }
}


public class MySubClass extends MySuperClass{

    @Override
    public void doTheThing() {
        System.out.println("Do it differently");
    }
}

2魂莫、@ Deprecated

@Deprecate 標(biāo)記類(lèi)还蹲、方法、屬性耙考,如果上述三種元素不再使用谜喊,使用@Deprecated注解,建議用戶不再使用

如果代碼使用了@Deprecate 注解的類(lèi)倦始、方法或?qū)傩远范簦幾g器會(huì)進(jìn)行警告。
舉例如下:

@Deprecated
public class MyComponent {
}

當(dāng)我們使用@Deprecate注解后鞋邑,建議配合使用對(duì)應(yīng)的@deprecated JavaDoc 符號(hào)诵次,并解釋說(shuō)明為什么這個(gè)類(lèi),方法或?qū)傩员粭売妹锻耄呀?jīng)替代方案是什么逾一?如下:

@Deprecated
/**
  @deprecated This class is full of bugs. Use MyNewComponent instead.
*/
public class MyComponent {
}

3、@SuppressWarnings

@SuppressWarnings 用來(lái)抑制編譯器生成警告信息肮雨∽穸拢可以修飾的元素為類(lèi),方法怨规,方法參數(shù)陌宿,屬性,局部變量波丰。

使用場(chǎng)景:當(dāng)我們一個(gè)方法調(diào)用了棄用的方法或者進(jìn)行不安全的類(lèi)型轉(zhuǎn)換壳坪,編譯器會(huì)生成警告。我們可以為這個(gè)方法增加@SuppressWarnings
注解掰烟,來(lái)抑制編譯器生成警告爽蝴。

PS:使用@SuppressWarnings注解沐批,采用就近原則,比如一個(gè)方法出現(xiàn)警告霜瘪,我們盡量使用@SuppressWarnings注解這個(gè)方法珠插,而不是注解方法所在的類(lèi)。雖然兩個(gè)都能抑制編譯器生成警告颖对,但是范圍越小越好捻撑,因?yàn)榉秶搅耍焕谖覀儼l(fā)現(xiàn)該類(lèi)下其他方法的警告信息缤底。
舉例如下:

@SuppressWarnings
public void methodWithWarning() {
}

四顾患、自定義注解

1、注解格式

了解完系統(tǒng)注解之后个唧,我們就可以自己定義注解了江解,通過(guò)上面的@Override的實(shí)例,不難看出定義注解的格式如下:

public @Interface 注解名{定義體}

PS:定義體就是方法的集合徙歼,每個(gè)方法實(shí)則是生命了一個(gè)配置參數(shù)犁河,方法的名稱作為配置參數(shù)的名稱,方法的返回值類(lèi)型就是配置參數(shù)的類(lèi)型魄梯,和普通的方法不一樣桨螺,可以通過(guò)default關(guān)鍵字來(lái)聲明配置參數(shù)的默認(rèn)值。
注意:

  • 1酿秸、注解類(lèi)型是通過(guò)"@interface"關(guān)鍵字定義的
  • 2灭翔、此處只能使用public或者默認(rèn)的default兩個(gè)權(quán)限修飾符
  • 3、配置參數(shù)的類(lèi)型只能使用基本類(lèi)型(byte,boolean,char,short,int,long,float,double和String辣苏,Enum肝箱,Class,annotation)
  • 4稀蟋、對(duì)于只含有一個(gè)配置參數(shù)的注解煌张,參數(shù)名建議設(shè)置中value,即方法名為value.
  • 5、配置參數(shù)一旦設(shè)置糊治,其參數(shù)值必須有確定的值唱矛,要不在使用注解的時(shí)候指定,要不在定義注解的時(shí)候使用default為其設(shè)置默認(rèn)值井辜,對(duì)于非基本類(lèi)型的參數(shù)值來(lái)說(shuō),其不能為null管闷。

2粥脚、創(chuàng)建自己的注解

在Java中,我們可以創(chuàng)建自己的注解包个,注解和類(lèi)刷允,接口文件一樣定義在自己的文件里面冤留。如下:

@interface MyAnnotation {
   String   name();
   int      age();
   String   sex();
 
 
}

上述代碼定義了一個(gè)叫做MyAnnotation的注解,它有4個(gè)元素树灶。再次強(qiáng)調(diào)一下纤怒,@Interface 這個(gè)關(guān)鍵字 用來(lái)告訴java編譯器這是一個(gè)注解。
應(yīng)用舉例

@MyAnnotation(
    name="張三",
    age=18,
    sex="男"
)
public class MyClass {
}

注意天通,我們需要為所有的注解元素設(shè)置值泊窘,一個(gè)都不能少。

3像寒、自定義注解默認(rèn)值

對(duì)于注解總的元素烘豹,我們可以為其設(shè)置默認(rèn)值,使用方法如下:

@interface MyAnnotation {
    String   name();
    int      age();
   String   sex() default "男";
}

上述代碼诺祸,我們?cè)O(shè)置了sex元素的默認(rèn)值為"男"携悯。當(dāng)我們?cè)谑褂脮r(shí),可以不設(shè)置sex的值筷笨,即讓value使用空字符串默認(rèn)值憔鬼。舉例如下;

@MyAnnotation(
    name="Jakob",
    age=37,
)
public class MyClass {
}

五胃夏、注解的原理

1轴或、注解處理器

如果沒(méi)有用來(lái)讀取注解的方法和工作,那么注解也就不會(huì)比注釋更有用戶了构订,使用注解的過(guò)程中侮叮,很重要的一部分就是創(chuàng)建與使用注解處理器。Java SE 擴(kuò)展了反射機(jī)制的API悼瘾,以幫助程序員快速的構(gòu)造自定義注解處理器囊榜。

2、注解處理器的分類(lèi)

我們已經(jīng)知道了如何自定義注解亥宿,當(dāng)時(shí)想要注解發(fā)揮實(shí)際作用卸勺,需要我們?yōu)樽⒔饩帉?xiě)響應(yīng)的注解處理器,根據(jù)注解的特性烫扼,注解處理器可以分為運(yùn)行時(shí)注解處理器編譯時(shí)注解處理器曙求。運(yùn)行時(shí)注解處理器需要借助反射機(jī)制實(shí)現(xiàn),而編譯時(shí)處理器則需要借助APT來(lái)實(shí)現(xiàn)映企。

無(wú)論是運(yùn)行時(shí)注解處理器還是編譯時(shí)注解處理器悟狱,主要工作都是讀取注解及處理特定主機(jī),從這個(gè)角度來(lái)看注解處理器還是非常容易理解的堰氓。

3挤渐、運(yùn)行時(shí)注解處理器

熟悉Java反射機(jī)制的同學(xué)一定對(duì)java.lang.reflect包非常熟悉,該包中的所有API都支持讀取運(yùn)行時(shí)Annotation的能力双絮,即屬性為@Retention(RetentionPolicy.RUNTIME)的注解浴麻。

在Java.lang.reflect中中的AnnotatedElement接口是所有程序元素的(Class得问、Method)父接口,我們可以通過(guò)反射獲取到某個(gè)類(lèi)的AnnotatedElement對(duì)象软免,進(jìn)而可以通過(guò)該對(duì)象提供的方法訪問(wèn)Annotation信息宫纬,常用的方法如下:

方法 含義
< T extends Annotation > T getAnnotation(Class <T> annotationClass) 表示返回該元素上存在的定制類(lèi)型的注解
Annotation[] getAnnotations() 返回該元素上存在的所有注解
default <T extends Annotation> T[] getAnnotationsByType(Class <T> annotationClass) 返回該元素制定類(lèi)型的注解
default <T extends Annotation> T getDeclaredAnnotation( Class <T> annotationClass) 返回直接存在與該元素上的所有注解
default <T extends Annotation>T[] getDeclaredAnntationsByType(Class <T> annotationClass) 返回直接存在該元素上某類(lèi)型的注解
Annotation[] getDeclaredAnnotations() 返回該元素上的所有注解

舉例說(shuō)明
一個(gè)User實(shí)體類(lèi)

public class User {
    private int id;
    private int age;
    private String name;

    @UserData(id=1,name="張三",age = 10)
    public User() {
    }
    public User(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }
  //...省略setter和getter方法
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

我們希望可以通過(guò)@UserData(id=1,name="張三",age = 10)這個(gè)注解,來(lái)為設(shè)置User實(shí)例的默認(rèn)值膏萧。
自定義注解如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CONSTRUCTOR)
public @interface UserData {
    public int id() default 0;
    public String name() default "";
    public int age() default 0;
}

該注解類(lèi)作用于構(gòu)造方法漓骚,并在運(yùn)行時(shí)存在,這樣我們就可以在運(yùn)行時(shí)通過(guò)反射獲取注解進(jìn)而為User實(shí)例設(shè)值向抢,看看如何處理該注解
運(yùn)行時(shí)注解處理器:

public class AnnotationProcessor {

    public static void init(Object object) {

        if (!(object instanceof User)) {
            throw new IllegalArgumentException("[" + object.getClass().getSimpleName() + "] isn't type of User");
        }

        Constructor[] constructors = object.getClass().getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            if (constructor.isAnnotationPresent(UserMeta.class)) {
                UserMeta userFill = (UserMeta) constructor.getAnnotation(UserMeta.class);
                int age = userFill.age();
                int id = userFill.id();
                String name = userFill.name();
                ((User) object).setAge(age);
                ((User) object).setId(id);
                ((User) object).setName(name);
            }
        }
    }
}

測(cè)試代碼

public class Main {

    public static void main(String[] args) {
        User user = new User();
        AnnotationProcessor.init(user);
        System.out.println(user.toString());
    }
}

運(yùn)行測(cè)試代碼认境,便得到我們想要的結(jié)果:

User{id=1, age=10, name=’dong’}

這里通過(guò)反射獲取User類(lèi)聲明的構(gòu)造方法,并檢測(cè)是否使用了@UserData注解挟鸠。然后從注解中獲取參數(shù)值并將其復(fù)賦值給User對(duì)象叉信。
正如上面所說(shuō),運(yùn)行時(shí)注解處理器的編寫(xiě)本質(zhì)上就是通過(guò)反射獲取注解信息艘希,隨后進(jìn)行其他操作硼身。編譯一個(gè)運(yùn)行時(shí)注解處理器就是那么簡(jiǎn)答。運(yùn)行時(shí)注解通常多用于參數(shù)配置模塊覆享。

4佳遂、編譯時(shí)注解處理器

不同于運(yùn)行時(shí)注解處理器,編寫(xiě)編譯時(shí)注解處理器(Annotation Processor Tool)撒顿。
APT 用于編譯時(shí)期掃描和處理注解信息丑罪,一個(gè)特定的注解處理器可以以Java源文件或編譯后的class文件作為輸入,然后輸出另一些文件凤壁,而已是.java文件吩屹,也可以是.class文件,但通常我們輸出的是.java文件拧抖。(注意:并不是對(duì)源文件進(jìn)行修改)煤搜,這些java文件會(huì)和其他源文件一起被javac編譯。
你可能會(huì)很納悶唧席,注解處理器是到底在什么階段介入的呢擦盾?好吧,其實(shí)是在javac開(kāi)始編譯之前淌哟,這就是通常我們?yōu)槭裁丛敢廨敵?java文件的原因迹卢。

注解最早是在java 5引入的,主要包含APT和com.sum.mirror包中現(xiàn)相關(guān)mirror api徒仓,此時(shí)APT和javac是各自獨(dú)立的婶希,但是從Java 6開(kāi)始,注解處理器正式標(biāo)準(zhǔn)化蓬衡,APT工具也被直接集成在javac當(dāng)中喻杈。

編譯時(shí)注解處理器編譯一個(gè)注解時(shí),主要分2步

  • 1狰晚、 繼承AbstractProcessor筒饰,實(shí)現(xiàn)自己的注解處理器
  • 2、注冊(cè)處理器壁晒,并打包成jar

舉例說(shuō)明:
首先來(lái)看一下一個(gè)標(biāo)準(zhǔn)的注解處理器的格式:

public class MyAnnotationProcessor extends AbstractProcessor {

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }
}

來(lái)簡(jiǎn)單的了解下其中5個(gè)方法的作用

方法 作用
init(ProcessingEnvironment processingEnv) 該方法有注解處理器自動(dòng)調(diào)用瓷们,其中ProcessingEnvironment類(lèi)提供了很多有用的工具類(lèi):Filter,Types秒咐,Elements谬晕,Messager等
getSupportedAnnotationTypes() 該方法返回字符串的集合表示該處理器用于處理那些注解
getSupportedSourceVersion() 該方法用來(lái)指定支持的Java版本,一般來(lái)說(shuō)我們都是支持到最新版本携取,因此直接返回SourceVersion.latestSupported()即可
process(Set annotations, RoundEnvironment roundEnv) 該方法是注解處理器處理注解的主要地方攒钳,我們需要在這里寫(xiě)掃描和處理注解的代碼,以及最終生成的java文件雷滋。其中需要深入的是RoundEnvironment類(lèi)不撑,該用于查找出程序元素上使用的注解

編寫(xiě)一個(gè)注解處理器首先要對(duì)ProcessingEnvironment和RoundEnvironment非常熟悉。接下來(lái)我們來(lái)了解下這兩個(gè)類(lèi)晤斩,先看下ProcessingEnvironment類(lèi):

public interface ProcessingEnvironment {

    Map<String,String> getOptions();

    //Messager用來(lái)報(bào)告錯(cuò)誤焕檬,警告和其他提示信息
    Messager getMessager();

    //Filter用來(lái)創(chuàng)建新的源文件,class文件以及輔助文件
    Filer getFiler();

    //Elements中包含用于操作Element的工具方法
    Elements getElementUtils();

     //Types中包含用于操作TypeMirror的工具方法
    Types getTypeUtils();

    SourceVersion getSourceVersion();

    Locale getLocale();
}

重點(diǎn)來(lái)認(rèn)識(shí)一下Element澳泵,Types和Filer实愚。Element(元素)是什么呢?

Element

element表示一個(gè)靜態(tài)的兔辅,語(yǔ)言級(jí)別的構(gòu)件腊敲。而任何一個(gè)結(jié)構(gòu)化文檔都可以看作是由不同的element組成的結(jié)構(gòu)體幢妄,比如XML兔仰,JSON等。這里我們用XML來(lái)示例:

<root>
  <child>
    <subchild>.....</subchild>
  </child>
</root>

這段xml中包含了三個(gè)元素:<root>蕉鸳、<child>潮尝、<subchild>到現(xiàn)在你已經(jīng)明白元素是什么。對(duì)java源文件來(lái)說(shuō)勉失,他同樣是一種結(jié)構(gòu)化文檔:

package com.demo;             //PackageElement
public class Main{                  //TypeElement
    private int x;                  //VariableElement
    private Main(){                 //ExecuteableElement
    }
    private void print(String msg){ //其中的參數(shù)部分String msg TypeElement
    }
}

對(duì)于java源文件來(lái)說(shuō)羹蚣,Element代表程序元素:包,類(lèi)乱凿,方法都是一種程序元素顽素。另外如果你對(duì)網(wǎng)頁(yè)解析工具jsoup熟悉咽弦,你會(huì)覺(jué)得操作此處的element是非常容易,關(guān)于jsoup不在本文講解之內(nèi)胁出。

接下來(lái)看看各種Element之間的關(guān)系圖型型,以便有個(gè)大概的了解


element關(guān)系圖.png
元素 含義
VariableElement 代表一個(gè) 字段, 枚舉常量, 方法或者構(gòu)造方法的參數(shù), 局部變量及 異常參數(shù)等元素
PackageElement 代表包元素
TypeElement 代表類(lèi)或接口元素
ExecutableElement 代碼方法,構(gòu)造函數(shù)全蝶,類(lèi)或接口的初始化代碼塊等元素闹蒜,也包括注解類(lèi)型元

TypeMirror、TypeElement抑淫、DeclaredType 這三個(gè)類(lèi)我也簡(jiǎn)單的介紹下:

  • TypeMirror:代表Java語(yǔ)言中類(lèi)型.Types包括基本類(lèi)型绷落,聲明類(lèi)型,數(shù)組,類(lèi)型變量和空類(lèi)型始苇。也代表通配類(lèi)型參數(shù)砌烁,可執(zhí)行文件的簽名和返回類(lèi)型等.
  • TypeElement 代表類(lèi)或接口元素
  • DeclaredType 代表類(lèi)型或接口類(lèi)型

簡(jiǎn)單的來(lái)說(shuō),Element代表源代碼埂蕊,TypeElement代表的是源碼中的類(lèi)型元素往弓,比如類(lèi),雖然我們可以從TypeElement中獲取類(lèi)名蓄氧,TypeElement中不包含類(lèi)本身的信息函似,比如它的父類(lèi),要想獲取這信息需要借助TypeMirror喉童,可以通過(guò)的Element中的asType()獲取元素對(duì)應(yīng)的TypeMirror撇寞。

Filer

Filter 用于注解處理器中創(chuàng)新文件。

然后看一下RoundEnvironment這個(gè)類(lèi)堂氯,這個(gè)類(lèi)比較簡(jiǎn)單

public interface RoundEnvironment {

    boolean processingOver();

     //上一輪注解處理器是否產(chǎn)生錯(cuò)誤
    boolean errorRaised();

     //返回上一輪注解處理器生成的根元素
    Set<? extends Element> getRootElements();

   //返回包含指定注解類(lèi)型的元素的集合
    Set<? extends Element> getElementsAnnotatedWith(TypeElement a);

    //返回包含指定注解類(lèi)型的元素的集合
    Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a);
}

然后來(lái)看一下RoundEnvironment,這個(gè)類(lèi)比較簡(jiǎn)單,一筆帶過(guò):
public interface RoundEnvironment { boolean processingOver(); //上一輪注解處理器是否產(chǎn)生錯(cuò)誤 boolean errorRaised(); //返回上一輪注解處理器生成的根元素 Set<? extends Element> getRootElements(); //返回包含指定注解類(lèi)型的元素的集合 Set<? extends Element> getElementsAnnotatedWith(TypeElement a); //返回包含指定注解類(lèi)型的元素的集合 Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a);}

Filer
Filer用于注解處理器中創(chuàng)建新文件蔑担。具體用法在下面示例會(huì)做演示.另外由于Filer用起來(lái)實(shí)在比較麻煩,后面我們會(huì)使用javapoet簡(jiǎn)化我們的操作.
好了,關(guān)于AbstractProcessor中一些重要的知識(shí)點(diǎn)我們已經(jīng)看完了.假設(shè)你現(xiàn)在已經(jīng)編寫(xiě)完一個(gè)注解處理器了,下面,要做什么呢? |

打包并注冊(cè).

自定義的處理器如何才能生效那?為了讓Java編譯器找到自定義的注解處理器我們需要對(duì)其進(jìn)行注冊(cè)和打包:自定義的處理器需要被達(dá)成一個(gè)jar咽白,并且需要在jar包的META-INF/services路徑下中創(chuàng)建一個(gè)固定的文件
javax.annotation.processing.processor啤握,在javax.annotation.processing.Processor文件中需要填寫(xiě)自定義處理器的完整路徑名,有幾個(gè)處理器就要填寫(xiě)幾個(gè)
從Java 6之后晶框,我們只需要將打開(kāi)的jar防止到項(xiàng)目的buildpath下即可排抬,javac在運(yùn)行的過(guò)程會(huì)自動(dòng)檢查javax.annotation.processing.Processor注冊(cè)的注解處理器,并將其注冊(cè)上授段。而Java 5需要單獨(dú)使用APT工具蹲蒲。
最終我們需要獲得一個(gè)包含注解處理器的代碼的jar包

六、注解基礎(chǔ)知識(shí)思維導(dǎo)圖

最后借用下別人的Java注解的基礎(chǔ)知識(shí)點(diǎn)導(dǎo)圖


Java注解.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末侵贵,一起剝皮案震驚了整個(gè)濱河市届搁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖卡睦,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宴胧,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡么翰,警方通過(guò)查閱死者的電腦和手機(jī)牺汤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浩嫌,“玉大人,你說(shuō)我怎么就攤上這事补胚÷肽停” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵溶其,是天一觀的道長(zhǎng)骚腥。 經(jīng)常有香客問(wèn)我,道長(zhǎng)瓶逃,這世上最難降的妖魔是什么束铭? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮厢绝,結(jié)果婚禮上契沫,老公的妹妹穿的比我還像新娘。我一直安慰自己昔汉,他們只是感情好懈万,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著靶病,像睡著了一般会通。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上娄周,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天涕侈,我揣著相機(jī)與錄音,去河邊找鬼煤辨。 笑死裳涛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掷酗。 我是一名探鬼主播调违,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼泻轰!你這毒婦竟也來(lái)了技肩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虚婿,沒(méi)想到半個(gè)月后旋奢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡然痊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年至朗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剧浸。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锹引,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出唆香,到底是詐尸還是另有隱情嫌变,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布躬它,位于F島的核電站腾啥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏冯吓。R本人自食惡果不足惜倘待,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望组贺。 院中可真熱鬧凸舵,春花似錦、人聲如沸锣披。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)雹仿。三九已至增热,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胧辽,已是汗流浹背峻仇。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邑商,地道東北人摄咆。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像人断,于是被迫代替她去往敵國(guó)和親吭从。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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