插件annotationprocessor在本书《深入理解Java虚拟机》中有一些介绍(在前端编译文章中有提到),但一直没用过,在这里做一个记录。了解过lombok底层原理的人都知道,它使用的是插件注解,所以今天我将在真实场景中演示插件注解的使用。需求我们为公司提供一套通用的JAVA基础组件包。组件包中有不同的模块,比如fuse模块、loadaveraging模块、rpc模块等,这些模块会被打包成jar包,然后发布到公司内部代码库中,供其他人导入使用。这段代码会不断迭代。我们希望可以通过promethus来监控当前代码库在公司中使用各个版本的比例。想要的效果是这样的:我们希望看到各个版本的使用率,这样有利于我们做版本兼容,并且在需要的时候可以追溯早期版本的用户来源。问题需求看似很简单,但是真正拿到自己的jar版本号还是挺麻烦的。有一种比较简单但是地狱般的方式,就是给每个组件加上当前的jar版本号,写在配置文件中或者直接设置为常量,这样就可以直接获取到jar包的版本号了向普罗米修斯报告。这种方法虽然可以解决问题,但是每次迭代都要更改所有组件包的版本号数据,太麻烦了。有更好的解决方案吗?比如我们能否在gradle打包构建的时候获取到jar包的版本号,然后注入到各个组件中呢?就像lombok一样,不需要写get和set方法,只需要添加注释标记就可以自动注入get和set方法。例如,我们可以为每个组件定义一个空常量,添加自定义注解:@TrisceliVersionpublicstaticfinalStringversion="";然后像lombok一样注入真实的版本号生成set/get方法:@TrisceliVersionpublicstaticfinalStringversion="1.0.31-SNAPSHOT";参考lombok的实现,这个其实是可以做到的,下面看看解决方法。java中解析注解的方式主要有两种:编译时扫描和运行时反射。这是lombok@Setter的实现:@Target({ElementType.FIELD,ElementType.TYPE})@Retention(RetentionPolicy.SOURCE)public@interfaceSetter{//略...}可以看到@Setter的Retention是SOURCE类型的,也就是说这个注解只在编译后的一段时间内有效,甚至不会被编译到class文件中,所以lombok无疑是第一种解析方式,那么这个注解怎么解析呢并在编译时执行我们的解析代码?答案是定义可插入注释处理器(通过JSR-269提案定义的可插入注释处理API实现)。可插拔注解处理器的触发点如下图所示:也就是说,可插拔注解处理器可以帮助我们在编译时修改抽象语法树(AST)!所以现在我们只需要自定义这样一个处理器,然后获取里面的jar版本信息即可(因为是编译期,可以找到源码的路径,在源码中创建一个文件存放版本即可number,然后在That'sit)中使用javaio读取即可,然后将注解对应的语法树上的常量值设置为jar包的版本号。当语法树发生变化时,最终生成的字节码也会随之变化。这实现了我们想要在编译时注入常量版本的愿望。定制一个插件注解处理器也很简单。首先定义自己的注解:@Documented@Retention(RetentionPolicy.SOURCE)//只在编译时有效,最后不会进入class文件@Target({ElementType.FIELD})//只允许作用于类属性public@interfaceTrisceliVersion{}然后定义一个继承AbstractProcessor的处理器:私人TreeMakertreeMaker;私人处理环境处理环境;/***初始化处理器**@paramprocessingEnv提供一系列实用工具*/@SneakyThrows@OverridepublicEnvironingsynchronizedprocedc(void){super.init(processingEnv);this.processingEnv=processingEnv;this.javacTrees=JavacTrees.instance(processingEnv);上下文context=((JavacProcessingEnvironment)processingEnv).getContext();this.treeMaker=TreeMaker.instance(context);@OverridepublicSourceVersiongetSsupportedSourceVersion(){返回SourceVersion.latest();}@OverridepublicSet
