Java
注解的解析有两种方式:反射和注解处理器(APT/JSR 269
),本文主要介绍注解处理器解析方式。
- 解析方式
注解处理器解析注解的方式,是在编译期解析的,所以对注解的Rentention
没有要求(即使RetentionPolicy.SOURCE
也可以),可以根据编码需求自行设定。 - 工具属性
注解处理器是javac
的一个工具,它用来在编译时扫描和处理对应注解。一个注解的注解处理器,通常以Java
代码(或者编译过的字节码)作为输入,生成文件(通常是.java
文件)作为输出。这些生成的Java
代码是在新生成的.java
文件中,所以不能修改已经存在的Java
类,例如向已有的类中添加方法。这些生成的.java
文件,会同其他普通的手动编写的Java
源代码一样被javac
编译。 - 独立进程
注解处理器是运行它自己的虚拟机JVM
中的,javac
启动一个完整Java
虚拟机来运行注解处理器,所以可以把注解处理器库看做一个独立的Java
项目。
自定义注解处理器流程
自定义注解和自定义注解处理器建议分成两个 jar
包,自定义注解处理器仅仅在编译期需要用到,所以工程中不需要将处理器打包到目标代码中。
自定义注解库 Custom Annotation
在 AS
中新建 Java
库:File --> New --> New Module --> Java Libary
,新建 myanno
注解库。
Gradle
文件:
1 | // Java 库 |
注解源文件:
1 | // 注解字段 |
Gradle
中选择该库,编译生成对应的 jar: myanno
。
自定义注解处理器 Custom Processor
同样,需要新建一个自定义处理器 Java
库:processors
。自定义注解处理器有两个步骤:
- 自定义注解处理器必须继承
AbstractProcessor
- 库的资源文件夹中添加
javax.annotation.processing.Processor
文件
继承 AbstractProcessor
自定义注解处理器必须继承 AbstractProcessor
,使用 @SupportedAnnotationTypes
定义支持解析的注解,并重写 process
处理对应注解。
1 | "com.ymzs.annotation.Field"}) ({ |
添加 javax.annotation.processing.Processor
文件
为了将自定义注解处理器注册到 javac
时,必须打包一个特殊的文件 javax.annotation.processing.Processor
到 META-INF/services
目录下。原因见 Java 技术手册。
1 | // 源码目录结构 |
javax.annotation.processing.Processor
文件的内容是一个列表,每一行对应一个注解处理器的全称。例如:
1 | com.ymzs.parsing.AnnotationParsingProcessor |
Gradle
中选择该库,编译生成对应的 jar: processors
。
注解处理器插件
在 AS
中使用自定义注解处理器需要添加对应的 gradle
插件:
- 纯
Java
环境gradle
推荐的注解处理器插件为net.ltgt.apt
:Gradle 官网插件信息 ,插件的开源地址 gradle-apt-plugin, 使用方法参考: dagger, Annotation Processor in IntelliJ and Gradle 。
使用该插件时,被注解代码工程的gradle
文件中需要包含如下信息,关键词annotationProcessor
:
1 | plugins{ |
Android
环境Google
在发布AS
时,开发了对应的android-gradle-plugin
,这是一系列插件包,并包含注解处理器插件:Android 注解处理器插件使用方法 。
使用该插件时,被注解代码工程的gradle
文件中需要包含如下信息,关键词annotationProcessor
:
1 | // 使用 android-gradle-plugin 插件中的 application |
结果验证
在 Gradle Projects
窗口中,选择需要被注解代码工程,选择 Tasks --> build --> assemble
编译工程,能正确的打印自定义注解处理器中的 Log
:
1 | // 代码工程编译期,解析注解 |
APT
和 JSR269
APT
APT: Annotation-Processing Tool
,相关API
都在com.sun.mirror
包下。从Java 7
开始就被降级了,在Java 8
被彻底移除,APT 移除原因 。JSR269
JSR 269 API: Pluggable Annotation Processing API
,相关API
都在javax.annotation.processing, javax.lang.model
包下。从Java 6
开始引入,后续版本逐步代替了APT
。JSR 269
不管是那种 API
,它们都是处理注解的工具,对源代码文件进行检测,并找出对应注解后解析(或生成对应文件)。通常用来简化开发者的工作量,或者生成附属文件。虽然 APT
被移除了,但是因为历史原因,注解处理器还是常用 APT
来称呼。本文都是基于 JSR 269 API
实现的自定义注解处理器。
AnnotatedConstruct
体系
类图结构
AnnotatedConstruct
基本类
1 | public interface AnnotatedConstruct { |
TypeMirror
体系
1 | public enum TypeKind { |
Element
体系
1 | // 表示元素类别 |
源代码的每一个部分都是一个特定类型的 Element
,也就是说 Element
代表程序的任意元素,例如包、类、方法等。每个 Element
代表一个静态的、语言级别的构件。
1 | package com.example; // PackageElement |
换个角度来看源代码,它只是结构化的文本,不是可运行的。可以想象它就像要被解析的 XML
文件一样(或者是编译器中抽象的语法树)。就像 XML
解释器一样,有一些类似 DOM
的元素,可以从一个元素导航到它的父或者子元素上。
举例来说,假如有一个代表 public class Foo
类的 TypeElement
元素,可以遍历它的所有元素:
1 | TypeElement fooClass = ... ; |
Elements
接口
用来处理 Element
的工具类,Elements JavaDoc 。
1 | public interface Elements { |
Types
接口
用来处理 TypeMirror
的工具类,Types JavaDoc 。
1 | public interface Types { |
Filer
接口
用来创建文件,Filer JavaDoc 。
1 | public interface Filer { |
Messager
接口
提供给注解处理器一个报告错误、警告以及提示信息的途径。Messager JavaDoc , 消息级别 Diagnostic.Kind 。
因为注解处理器是在独立的 java
虚拟机运行,所以注解处理器中不能直接进行异常抛出,否则进程异常崩溃时,会弹出一大堆让人捉摸不清的堆栈调用日志显示,也就是目标工程报的错误来自于另外一个虚拟机的堆栈。通常使用 Messager
来写一些信息给使用此注解器的第三方开发者的。在官方文档中描述了消息的不同级别,非常重要的是 Kind.ERROR
,因为这种类型的信息用来表示我们的注解处理器处理失败了,很有可能是第三方开发者错误的使用了注解。
1 | // 打印信息 |
AbstractProcessor
详细分析
自定义处理器通常会继承 AbstractProcessor
,并重写对应方法,来实现自定义注解的解析。所有的注解处理器类都必须有一个无参构造函数,否则执行时会报错。
对应源码文件目录:src\javax\annotation\processing
类图结构
常用 API
介绍
1 | public interface Processor { |
两个有用的注解
- @SupportedAnnotationTypes
表示当前类支持的注解的完整类路径,支持通配符。当前Processor
要处理的Annotation
名字全称。等同于getSupportedAnnotationTypes()
,支持通配符,使用*
表示支持所有注解。 - @SupportedSourceVersion
表示该处理器支持的源码版本。等同于getSupportedSourceVersion()
。
循环调用 process
process
通常会被执行两次:根据 Log
可以发现,process
的返回值不管是 true/false
都会被执行两次?但是解析注解时,只有第一次执行 process
时,roundEnvironment.getElementsAnnotatedWith
才能获取到注解。
具体原因参考 Processor JavaDoc 。官方文档中第一段就介绍了,注解处理器是个循环处理的过程,每次循环都会解析上次处理器产生的源文件。也就是说第一次 process
处理了编写的源文件,第二次处理了注解处理器生成的源文件(此时已经不包含被处理的注解了),直到所有支持的注解都被解析完毕。
存在的问题
不管是使用 @SupportedAnnotationTypes
还是重写 getSupportedAnnotationTypes()
,只要支持注解包其中一个注解,注解处理器就能解析该包中的所有其他注解。
1 | // Java library: myanno |
注解包 myanno
包含三个注解,但是在自定义注解器中,代码中规定只支持 Field
注解,自定义注解器仍然能解析 Info, AnyAnnotation
?
注解处理器插件
gradle-apt-plugin
Java
环境下注解插件,使用方法:1
2
3
4
5
6
7
8
9plugins{
id "net.ltgt.apt" version "0.15"
}
dependencies {
// 本地注解库
annotationProcessor project(":processors")
// 引用网络开源注解库
annotationProcessor "com.custom:CustomProcessors:version1.2.3"
}android-apt
Android
注解器框架,现在已经不再维护。annotationProcessor
Android Plugin for Gradle
,google
为gradle
开发的注解处理器框架,用于替换android-apt
。使用方法:1
2
3
4// 本地注解库
annotationProcessor ":customProcessors"
// 引用网络开源注解库
annotationProcessor "com.custom:CustomProcessors:version1.2.3"
常用开源库
AutoService
官网
自动生成javax.annotation.processing.Processor
文件,并将自定义注解处理器按照规范加入该文件。JavaPoet
官网
生成.java
源文件的API
接口,非常强大实用。解析自定义注解时,可以使用JavaPoet
来生成对应文件。
AutoService
用法
build.gradle
文件中添加依赖implementation 'com.google.auto.service:auto-service:1.0-rc4'
具体版本号,到github
官网上查看。- 自定义注解处理器类上添加
AutoService
注解
在自定义处理器类上,添加@AutoService(Processor.class)
注解。该注解会自动生成对应的META-INF/services/javax.annotation.processing.Processor
文件,并将自定义注解处理器按照规范加入该文件。
JavaPoet
用法
其他
调试注解处理器
常用场景
大量开源库都使用了注解处理器简化代码:
参考文档
- Java 注解处理器
- Annotation Processing 101
- AnnotatedConstruct 常用接口介绍
- 如何调试编译时注解处理器
- APT 移除原因
- JSR 269
- Annotation 原理到案例
- 自定义注解之编译时注解
- javax.annotation.processing.Processor 文件 Java 技术手册
- Gradle 官网 Java apt 插件信息
- gradle-apt-plugin
- Annotation Processor in IntelliJ and Gradle
- Android 注解处理器插件使用方法
- Android 打造编译时注解解析框架
- Android 注解使用之 annotationProcessor
- AutoService 官网
- JavaPoet 官网
- 几个注解处理器名称