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/