整體Retrofit內(nèi)容如下:
- 1洞渤、Retrofit解析1之前哨站——理解RESTful
- 2醉蚁、Retrofit解析2之使用簡(jiǎn)介
- 3拉背、Retrofit解析3之反射
- 4住闯、Retrofit解析4之注解
- 5断箫、Retrofit解析5之代理設(shè)計(jì)模式
- 6拂酣、Retrofit解析6之面向接口編程
- 7、Retrofit解析7之相關(guān)類(lèi)解析
- 8仲义、Retrofit解析8之核心解析——ServiceMethod及注解1
- 9婶熬、Retrofit解析8之核心解析——ServiceMethod及注解2
- 10、Retrofit解析9之流程解析
- 11埃撵、Retrofit解析10之感謝
由于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è)大概的了解
元素 | 含義 |
---|---|
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)圖