Annotation原理&自定制

Annotation是特殊的interface瑞躺,從java5開始引入。

因為Annotation的實現(xiàn)(processor部分)涉及到java編譯流程,所以如果想深入了解份招,需要對java編譯器的和編譯過程有一定了解丈攒。

通過annotation可以實現(xiàn)源代碼的inspect, modify or generate哩罪∈诎裕可以幫助Developer做一些檢查,修改源碼(雖然一般說annotation僅限于訪問和檢查已經(jīng)存在的源文件际插,生成新文件碘耳,而不能修改既有java文件,但是依然可以通過java內部非公開的compiler API在編譯階段修改AST樹(AST-Transforming)來達到修改java原文件的效果-lombok就是這樣實現(xiàn)的)框弛,生成文件(比如通過JavaPoet和Filer結合)等工作辛辨,減少Programmer在開發(fā)流程里大量重復然而與業(yè)務邏輯無關的代碼工作(比如Builder,Getter&Setter)瑟枫。

1. Annotation分類

a. annotation?

@Override愉阎,@SuppressWarnings,@Deprecated等常用注解力奋。(本文著重講關于annotation processor的部分榜旦,所以此處常用annotation不熟悉請自行查閱)

b. meta-annotation - 用來修飾annotation的annotation。

@Target景殷,@Retention溅呢,@Inherited,@Documented猿挚,@Repeatable

@Target限制annotation作用對象:取值為枚舉ElementType類型之一(ANNOTATION_TYPE咐旧,CONSTRUCTOR,F(xiàn)IELD绩蜻,LOCAL_VARIABLE铣墨,METHOD,PACKAGE办绝,PARAMETER伊约,TYPE,TYPE_PARAMETER孕蝉,TYPE_USE)屡律。一個annotation可以聲明多個作用對象。

@Retention指定annotation的作用范圍降淮,RetentionPolicy.SOURCE是保留和作用在compiler之前超埋;RetentionPolicy.CLASS保留作用在compiler期間&load進入JVM之前;RetentionPolicy. RUNTIME一直保留到JVM加載類&執(zhí)行代碼佳鳖。

@Inherited為標識注解(標識注解霍殴,mark annotation,是指接口聲明里面沒有field存在的@interface)系吩,聲明此元注解的annotation可以在被應用的class上被子類繼承来庭。

2. @interface

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.SOURCE)

public @interface Builder {...}

可以有0+個field。如果沒有field淑玫,則稱為標識annotation巾腕;如果有一個,一般命名為{TYPE} value() default {DefaultValue}絮蒿;此時當使用賦值時不用顯式指明是哪個field; 如果有多個尊搬,按照field意思命名。{TYPE}可以是基礎類型土涝,Sting佛寿,Class,Annotation但壮,Enum和上述的Array冀泻;methods和constructor不可以在annotation里聲明。annotation不可以被繼承擴展蜡饵。

3. Annotation Processing

Java編譯器(一般為javac弹渔,也有其他compiler比如ant提供的)支持一個叫“注解處理器(annotation processor)”的插件(用-processor命令行參數(shù)),這個插件可以在編譯階段處理這些注解溯祸。注解處理器可以對注解做靜態(tài)代碼分析肢专,創(chuàng)建額外的java源文件或者其他文件,或者修改被注解的代碼(通過修改代碼的AST)焦辅,然后再交給編譯器編譯生成字節(jié)碼(.class文件)博杖。當有新java文件生成的時候,回到parser的階段處理新生成的java文件筷登。當沒有新java文件產生的時候剃根,執(zhí)行processor的下一步,分析和編譯(也是編譯器的核心部分)最終生成.class文件前方。

下面引用來自java的javax.annotation.processing.Processor的官方Reference:

The interface for an annotation processor. Annotation processing happens in a sequence of rounds. On each round, a processor may be asked to process a subset of the annotations found on the source and class files produced by a prior round. The inputs to the first round of processing are the initial inputs to a run of the tool; these initial inputs can be regarded as the output of a virtual zeroth round of processing. If a processor was asked to process on a given round, it will be asked to process on subsequent rounds, including the last round, even if there are no annotations for it to process. The tool infrastructure may also ask a processor to process files generated implicitly by the tool's operation.

1. Each implementation of a Processor must provide a public no-argument constructor to be used by tools to instantiate the processor. The tool infrastructure will interact with classes implementing this interface as follows:

2. If an existing Processor object is not being used, to create an instance of a processor the tool calls the no-arg constructor of the processor class.

3. Next, the tool calls the init method with an appropriate ProcessingEnvironment.

4. Afterwards, the tool calls getSupportedAnnotationTypes, getSupportedOptions, and getSupportedSourceVersion. These methods are only called once per run, not on each round.

5. As appropriate, the tool calls the process method on the Processor object; a new Processor object is not created for each round.

If a processor object is created and used without the above protocol being followed, then the processor's behavior is not defined by this interface specification.

The tool uses a discovery process to find annotation processors and decide whether or not they should be run. By configuring the tool, the set of potential processors can be controlled. For example, for a Java Compiler the list of candidate processors to run can be set directly or controlled by a search path used for a service-style lookup. Other tool implementations may have different configuration mechanisms, such as command line options; for details, refer to the particular tool's documentation. Which processors the tool asks to run is a function of what annotations are present on the root elements, what annotation types a processor processes, and whether or not a processor claims the annotations it processes. A processor will be asked to process a subset of the annotation types it supports, possibly an empty set. For a given round, the tool computes the set of annotation types on the root elements. If there is at least one annotation type present, as processors claim annotation types, they are removed from the set of unmatched annotations. When the set is empty or no more processors are available, the round has run to completion. If there are no annotation types present, annotation processing still occurs but only universal processors which support processing "*" can claim the (empty) set of annotation types.

Note that if a processor supports "*" and returns true, all annotations are claimed. Therefore, a universal processor being used to, for example, implement additional validity checks should return false so as to not prevent other such checkers from being able to run.

自定義的Annotation Processor需要實現(xiàn)javax.annotation.processing.Processor接口狈醉,or繼承實現(xiàn)了此接口&提供了部分邏輯的AbstractProcessor抽象類(perfer)。

@SupportedAnnotationTypes("com.baeldung.annotation.processor.Builder")//指明此processor要處理哪些annotation惠险,可為" * "舔糖。?

@SupportedSourceVersion(SourceVersion.RELEASE_8) //注意processor是與特定Java version緊密相關的

public builderProcessor extends AbstractProcessor {

? ? @Override

?//roundEnvironment包含了每一次processing round的環(huán)境信息,相當于上下文

????public boolean process(Set<? extends TypeElement>?annotations,?RoundEnvironment roundEnv) {...}

return false; //表示此annotation依然需要在其他round被其他processor去處理莺匠。

//return true; //表示此annotation已經(jīng)被處理金吗,不再需要其他processor去處理了。

}

4. Compile & Plugin

Java編譯器通過提供-processor命令行參數(shù)趣竣,來把外部定義的annotation processors插入到編譯階段(注意摇庙,annotation processor的邏輯代碼需要提前編譯好):

javac -cp processors/target/****.processors.jar (path to find processor)

????-processor com.***.**Processor (processor's whole name - with packageName) ?

? ? -d directory/to/put/classfile

????examples/src/main/java/com/***/ToCompileClass.java (java file to compile)

上述方法編譯一兩個文件不復雜,但是一個項目有成千上百的java文件需要編譯遥缕,此時命令行參數(shù)的做法就有點捉襟見肘了卫袒。

不過代碼社區(qū)已經(jīng)開發(fā)了好多很棒的build tools:Maven,Gradle单匣,sbt夕凝,Ant等宝穗,都可以觸發(fā)Java Compiler去做很多事情。

比如码秉,上述命令行參數(shù)可以對等的改成如下對Maven項目中pom.xml文件的修改:

<plugin>

? ? <groupId>org.apache.maven.plugins</groupId>

? ? <artifactId>maven-compiler-plugin</artifactId>

? ? <version>3.1</version>

? ? <configuration>

? ? ? ? <source>1.8</source>

? ? ? ? <target>1.8</target>

? ? ? ? <annotationProcessors> <proc>com.****.**Processor</proc> </annotationProcessors>

? ? </configuration>

</plugin>

5. Example

綜上所述逮矛,想要自定制annotation,必須實現(xiàn)兩部分:@interface的annotation聲明转砖,和annotation processor须鼎。然后通過javac -processor編譯代碼或者通過第三方插件&IDE自動編譯代碼。

https://www.javacodegeeks.com/2015/09/java-annotation-processors.html提供了三個很棒的示例府蔗,分別是代碼檢查晋控,源碼修改和文件生成。建議讀者自己運行一下各個示例姓赤,以加深理解赡译。

修改源碼也可以分為兩部分:修改已經(jīng)存在的方法和屬性;添加新的方法或者屬性不铆。前者很容易捶朵,通過對ast節(jié)點的訪問,找到被標注的node狂男,修改該node的修飾符等等就可以综看;后者就必須采用java內部ast transformation相關的api來解決,因為public api并沒有提供添加刪除節(jié)點相關的api岖食。

6. Reference

https://www.javacodegeeks.com/2015/09/java-annotation-processors.html

https://www.javacodegeeks.com/2015/09/java-compiler-api.html

https://docs.oracle.com/javase/7/docs/api/javax/annotation/processing/Processor.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末红碑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子泡垃,更是在濱河造成了極大的恐慌析珊,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔑穴,死亡現(xiàn)場離奇詭異忠寻,居然都是意外死亡,警方通過查閱死者的電腦和手機存和,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門奕剃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人捐腿,你說我怎么就攤上這事纵朋。” “怎么了茄袖?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵操软,是天一觀的道長。 經(jīng)常有香客問我宪祥,道長聂薪,這世上最難降的妖魔是什么家乘? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮藏澳,結果婚禮上仁锯,老公的妹妹穿的比我還像新娘。我一直安慰自己笆载,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布涯呻。 她就那樣靜靜地躺著凉驻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪复罐。 梳的紋絲不亂的頭發(fā)上涝登,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音效诅,去河邊找鬼胀滚。 笑死,一個胖子當著我的面吹牛乱投,可吹牛的內容都是我干的咽笼。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼戚炫,長吁一口氣:“原來是場噩夢啊……” “哼剑刑!你這毒婦竟也來了?” 一聲冷哼從身側響起双肤,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤施掏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后茅糜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體七芭,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年蔑赘,在試婚紗的時候發(fā)現(xiàn)自己被綠了狸驳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡缩赛,死狀恐怖锌历,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情峦筒,我是刑警寧澤究西,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站物喷,受9級特大地震影響卤材,放射性物質發(fā)生泄漏遮斥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一扇丛、第九天 我趴在偏房一處隱蔽的房頂上張望术吗。 院中可真熱鬧,春花似錦帆精、人聲如沸较屿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隘蝎。三九已至,卻和暖如春襟企,著一層夾襖步出監(jiān)牢的瞬間嘱么,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工顽悼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留曼振,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓蔚龙,卻偏偏與公主長得像冰评,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子木羹,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內容

  • 如果不知道注解處理器(Annotation Processor),可以先查看一下我上一篇寫的注解處理器(Annot...
    Whyn閱讀 6,082評論 2 3
  • 本人一直秉持汇跨,事業(yè)無成务荆,娶親生子,兒孫滿堂的執(zhí)念穷遂,為此我還真悟出了不少育兒的道理函匕,這里來說于你聽。 長期以來蚪黑,為人...
    綿大錘閱讀 480評論 0 0
  • A3 有一家很好的餐廳盅惜,你平時會在那招待一些好朋友。你想邀請你的父母也去那兒吃飯忌穿,可是抒寂,他們知道那里很貴,心疼你花...
    玖品生活閱讀 207評論 0 0
  • 上一章·楔子 下一章 醉君顏·(2) 君國·未央宮 “奉天承運掠剑,皇帝昭曰:莘夫人蓄意與言國丞相謀害皇后屈芜,鑒于昔...
    桃殀閱讀 205評論 0 0
  • 【案例】 參考書目:《最重要的事,只有一件》 作者:加里·凱勒 【關鍵詞】 多米諾骨牌 帕累托法則:80%的結果得...
    yoni_文爺閱讀 185評論 0 0