Annotation原理&自定制

Annotation是特殊的interface,从java5开始引入。

因为Annotation的实现(processor部分)涉及到java编译流程,所以如果想深入了解,需要对java编译器的和编译过程有一定了解。

通过annotation可以实现源代码的inspect, modify or generate。可以帮助Developer做一些检查,修改源码(虽然一般说annotation仅限于访问和检查已经存在的源文件,生成新文件,而不能修改既有java文件,但是依然可以通过java内部非公开的compiler API在编译阶段修改AST树(AST-Transforming)来达到修改java原文件的效果-lombok就是这样实现的),生成文件(比如通过JavaPoet和Filer结合)等工作,减少Programmer在开发流程里大量重复然而与业务逻辑无关的代码工作(比如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,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER,TYPE,TYPE_PARAMETER,TYPE_USE)。一个annotation可以声明多个作用对象。

@Retention指定annotation的作用范围,RetentionPolicy.SOURCE是保留和作用在compiler之前;RetentionPolicy.CLASS保留作用在compiler期间&load进入JVM之前;RetentionPolicy. RUNTIME一直保留到JVM加载类&执行代码。

@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命令行参数),这个插件可以在编译阶段处理这些注解。注解处理器可以对注解做静态代码分析,创建额外的java源文件或者其他文件,或者修改被注解的代码(通过修改代码的AST),然后再交给编译器编译生成字节码(.class文件)。当有新java文件生成的时候,回到parser的阶段处理新生成的java文件。当没有新java文件产生的时候,执行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需要实现javax.annotation.processing.Processor接口,or继承实现了此接口&提供了部分逻辑的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的环境信息,相当于上下文

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

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

//return true; //表示此annotation已经被处理,不再需要其他processor去处理了。

}

4. Compile & Plugin

Java编译器通过提供-processor命令行参数,来把外部定义的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文件需要编译,此时命令行参数的做法就有点捉襟见肘了。

不过代码社区已经开发了好多很棒的build tools:Maven,Gradle,sbt,Ant等,都可以触发Java Compiler去做很多事情。

比如,上述命令行参数可以对等的改成如下对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,必须实现两部分:@interface的annotation声明,和annotation processor。然后通过javac -processor编译代码或者通过第三方插件&IDE自动编译代码。

https://www.javacodegeeks.com/2015/09/java-annotation-processors.html提供了三个很棒的示例,分别是代码检查,源码修改和文件生成。建议读者自己运行一下各个示例,以加深理解。

修改源码也可以分为两部分:修改已经存在的方法和属性;添加新的方法或者属性。前者很容易,通过对ast节点的访问,找到被标注的node,修改该node的修饰符等等就可以;后者就必须采用java内部ast transformation相关的api来解决,因为public api并没有提供添加删除节点相关的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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容