Java 注解处理器
工作流程
注解处理器是一种应用于编译期间的模块,在编译完源文件后,编译器会解析类信息,转换成抽象语法树,接着执行注册的注解处理器,解析语法树是否发生了变化并重新生成源文件,接着调用下一个注解处理器。
编译器具体处理过程可查看 OpenJDK 官方文档: https://openjdk.java.net/groups/compiler/doc/compilation-overview/index.html
-
定义注解 创建注解处理器的第一步就是需要定义相关注解,并且在注解上定义
@Retention
,当指定为RetentionPolicy.SOURCE
时,该注解即在编译结束后会被擦除。 -
创建处理器 接着就需要创建处理器了,一般可通过继承
javax.annotation.processing.AbstractProcessor
定义处理器,由于注解处理器是通过反射获取的,所以需要提供无参构造函数。 -
设置解析注解 重写
getSupportedAnnotationTypes
或定义@SupportedAnnotationTypes
将先前定义的注解类名加入解析目标,也可以使用*
通配符。 -
指定支持版本 对于存在源文件版本需求的处理器,则可以通过重写
SupportedSourceVersion
或定义@SupportedSourceVersion
来指定版本。 -
初始化处理器 注解处理器拥有许多可供使用的工具类,但是这些工具类需要通过
init
方法的ProcessingEnvironment
才可获取,一般做法也是重写此方法,提取所需工具对象保存至处理器中。 -
实现处理流程 注解处理器的核心流程为
process
方法,可通过参数RoundEnvironment
获取被注解标记的元素,实现想提供的功能,一般为动态创建源文件
或修改语法树
。 -
注册处理器 注册注解处理器的方式可以在编译时通过
javac -processor
指定。 也可以配置自动加载,依照ServiceLoader
形式,在META-INF/services
下创建名为javax.annotation.processing.Processor
的文件,将创建的注解处理器全类名填入,由于注解处理器是作用于编译期的,在编译时需要增加参数-proc:none
以不使用注解处理器。
创建文件
文件的创建需要通过 javax.annotation.processing.Filer
来实现,可通过 init
方法获取。
通过 Filer 可以新建源文件并获取 JavaFileObject
,以 Java 代码的方法将内容写入源文件。
MapStruct
和 JavaPoet
就是利用这个功能开发的工具。
修改语法树
对比创建文件,修改语法树则是十分复杂且麻烦的工作,同样要通过 init
获取语法树构造器 com.sun.source.util.Trees
。
通过 Trees 可以将元素转换为语法树,并接受 Visitor
以进行语法树节点扫描和修改,一般监视器的实现可以通过继承 TreeTranslator
或 TreeScanner
,对需要的方法进行重写。
而语法树操作的难点在于其他方面:
-
文档的缺少,这是无疑是对开发人员不友好的。
-
不稳定 api,有可能这个版本还能用的,在下一个版本就无效了。
-
未知的异常,可能由于操作失误而引发的,而且通过异常信息无法准确定位问题。
-
无法很好的控制注解处理器之间的顺序。
Lombok
就是一种修改语法树的工具。
这是我开发的一款通过修改语法树增加方法校验功能的工具: https://moyada.github.io/medivh/