插件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保留的是SOURCE类型,也就是说这个注解只在编译时有效,甚至不会被编译到class文件中,所以lombok无疑是第一个解析的方法,我们可以用什么方法来制作注释被解析并在编译时执行我们的解析代码?答案是定义插件注释处理器(由JSR-269提案定义的PluggableAnnotationProcessingAPI实现)。插件注解处理器的触发点如下图所示:也就是说插件注解处理器可以帮我们修改抽象语法树(AST)!所以现在我们只需要自定义这样一个处理器,然后获取里面的jar版本信息即可(因为是编译期,可以找到源码的路径,在源码中创建一个文件存放版本即可number,然后在That'sit)中使用javaio读取即可,然后将注解对应的语法树上的常量值设置为jar包的版本号。当语法树发生变化时,最终生成的字节码也会随之变化。这实现了我们想要在编译时注入常量版本的愿望。定制一个插件注解处理器也很简单。首先定义自己的注解:@Documented@Retention(RetentionPolicy.SOURCE)//只在编译时有效,最后不会进入class文件@Target({ElementType.FIELD})//只允许作用于类属性public@interfaceTrisceliVersion{}复制代码,定义一个继承AbstractProcessor的处理器:/**{@linkAbstractProcessor}属于PluggableAnnotationProcessingAPI*/publicclassTrisceliVersionProcessorextendsAbstractProcessor{privateJavacTreesjavacTrees;privateTreeMakertreeMaker;privateProcessingEnvironmentprocessingEnv;/***初始化处理器**@paramprocessingEnv提供一系列实用工具*/@SneakyThrows@OverridepublicEnchronizedEnchronizedcproviit{super.init(processingEnv);this.processingEnv=processingEnv;this.javacTrees=JavacTrees.instance(processingEnv);上下文context=((JavacProcessingEnvironment)processingEnv).getContext();this.treeMaker=TreeMaker.instance(context);}@OverridepublicSourceVersiongetSupportedSourceVersion(){returnSourceVersion.latest();}@OverridepublicSet
