编译时注解处理器的一次使用经历

这篇具有很好参考价值的文章主要介绍了编译时注解处理器的一次使用经历。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

编译时注解处理器在《深入理解Java虚拟机》一书中有一些介绍(前端编译篇有提到),但一直没有机会使用,直到碰到这个需求,觉得再合适不过了,就简单用了一下,这里做个记录。------原文写于2021年2月8日。

一、需求

我们为公司提供了一套通用的JAVA组件包,组件包内有不同的模块,这些模块会被打成jar包,然后发布到公司的内部代码库中,供其所有人引入使用。

这份代码会不断的迭代,我们希望可以通过promethus来监控现在公司内使用各版本代码库的比例,希望达到的效果图如下:

编译时注解处理器的一次使用经历
 图1-1

我们希望看到每一个版本的使用率,这有利于我们做版本兼容,必要的时候可以对古早版本使用者溯源。

二、问题

需求似乎很简单,但真要获取自身的jar版本号比较麻烦,有个比较简单但阴间的办法,就是给每一个组件都加上当前的jar版本号,写到配置文件里或者直接设置成常量,这样上报promethus时就可以直接获取到jar包版本号了,这个方法虽然可以解决问题,但每次迭代版本都要跟着改一遍所有组件包的版本号数据,过于麻烦。

有没有更好的解决办法呢?比如我们可不可以在gradle打包构建时拿到jar包的版本号,然后注入到每个组件中去呢?就像lombok那样,不需要写get、set方法,只需要加个注解标记就可以自动注入get、set方法。

比如我们可以给每个组件定义一个空常量,加上自定义的注解:


@TrisceliVersion
public static final String version = "";

然后像lombok生成set/get方法那样注入真正的版本号:


@TrisceliVersion
public static final String version = "1.0.31-SNAPSHOT";

参考lombok的实现,这其实是可以做到的,下面来看解决方案。

三、解决

java中解析一个注解的方式主要有两种:编译期扫描、运行期反射,这是lombok @Setter的实现:


@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Setter {
  	// 略...
}

可以看到@Setter的Retention是SOURCE类型的,也就是说这个注解只在编译期有效,所以lombok无疑是第一种解析方式,那用什么方式可以在编译期就让注解被解析到并执行我们的解析代码呢?答案就是定义编译时注解处理器通过JSR-269提案定义的Pluggable Annotation Processing API实现

插入式注解处理器的触发点如下图所示:

编译时注解处理器的一次使用经历
图3-1

也就是说编译时注解处理器可以帮助我们在编译期修改抽象语法树(AST)!所以现在我们只需要自定义一个这样的处理器,然后其内部拿到jar版本信息(因为是编译期,可以找到源码的path,读取源码中写有jar版本号的gradle文件,然后用java io读取进来解析拿到),再将注解对应语法树上的常量值设置成jar包版本号,语法树变了,最终生成的源代码也会跟着变,这样就实现了我们想在编译期给常量version注入值的愿望。

自定义一个编译时注解处理器很简单,先将自己的注解定义出来:


@Documented
@Retention(RetentionPolicy.SOURCE) //只在编译期有效,最终不会打进class文件中
@Target({ElementType.FIELD}) //仅允许作用于类属性之上
public @interface TrisceliVersion {
}

然后定义一个继承了AbstractProcessor的处理器: 


/**
 * {@link AbstractProcessor} 属于 Pluggable Annotation Processing API
 */
public class TrisceliVersionProcessor extends AbstractProcessor {

    private JavacTrees javacTrees;
    private TreeMaker treeMaker;
    private ProcessingEnvironment processingEnv;

    /**
     * 初始化处理器
     *
     * @param processingEnv 提供了一系列的实用工具
     */
    @SneakyThrows
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.processingEnv = processingEnv;
        this.javacTrees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
    }


    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }

    @Override
    public Set getSupportedAnnotationTypes() {
        HashSet set = new HashSet<>();
        set.add(TrisceliVersion.class.getName()); // 支持解析的注解
        return set;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement t : annotations) {
            for (Element e : roundEnv.getElementsAnnotatedWith(t)) { // 获取到给定注解的element(element可以是一个类、方法、包等)
                // JCVariableDecl为字段/变量定义语法树节点
                JCTree.JCVariableDecl jcv = (JCTree.JCVariableDecl) javacTrees.getTree(e);
                String varType = jcv.vartype.type.toString();
                if (!"java.lang.String".equals(varType)) { // 限定变量类型必须是String类型,否则抛异常
                    printErrorMessage(e, "Type '" + varType + "'" + " is not support.");
                }
                jcv.init = treeMaker.Literal(getVersion()); // 给这个字段赋值,也就是getVersion的返回值
            }
        }
        return true;
    }

    /**
     * 利用processingEnv内的Messager对象输出一些日志
     *
     * @param e element
     * @param m error message
     */
    private void printErrorMessage(Element e, String m) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, m, e);
    }

    private String getVersion() {
        /**
         * 获取version,为了演示,这里省略掉复杂的代码,直接返回固定值
         */
        return "v1.0.1";
    }
}

定义好的处理器需要被SPI机制发现,所以要定义META-INF.services:

编译时注解处理器的一次使用经历
图3-2

四、测试

新建测试模块,引入刚才写好的代码包:

编译时注解处理器的一次使用经历
图4-1

 

这是Test类:

编译时注解处理器的一次使用经历
图4-2

现在我们只需要让gradle build一下,新的字节码中该字段就有值了:

编译时注解处理器的一次使用经历
图4-3

这只是非常简单的一次尝试,既然它可以通过修改抽象语法树来控制生成的源代码,那么自然就有人会充分利用这一特性来实现一些非常好用的工具,比如lombok,我们再也不用写诸如set/get这种模板式的代码了。文章来源地址https://www.toymoban.com/news/detail-653911.html

到了这里,关于编译时注解处理器的一次使用经历的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 第三十二章 开发Productions - ObjectScript Productions - 定义警报处理器 - 使用路由警报处理器

    如果需要通过多种输出机制联系用户,警报处理器应该是一个业务流程,用于确定如何在消息中路由 Ens.AlertReques 。在这种情况下, Productions 必须为每个输出机制包含一个额外的业务操作,并且警报处理器将消息转发到这些业务操作。 要将警报处理器定义为路由流程,请创建

    2024年02月08日
    浏览(46)
  • 在枚举类中“优雅地”使用枚举处理器

    使用枚举类的一大好处就是,代码易懂,方便自己或他人维护。如,枚举状态、异常等。 下面有两个类(枚举类和实体类): Animal类中有一个用户状态字段: 应用如下: 第一步:把字段的类型改成我们的enum类,如下:  第二步:在枚举类中,在对应的字段是添加注解@Enu

    2024年02月07日
    浏览(39)
  • SpringBoot | RestTemplate异常处理器ErrorHandler使用详解

    关注wx:CodingTechWork   在代码开发过程中,发现很多地方通过 RestTemplate 调用了第三方接口,而第三方接口需要根据某些状态码或者异常进行重试调用,此时,要么在每个调用的地方进行异常捕获,然后重试;要么在封装的 RestTemplate 工具类中进行统一异常捕获和封装。当然

    2024年02月12日
    浏览(43)
  • Mybatis自定义枚举类处理器优雅地使用枚举

    在和前端开发对接接口过程中经常发现需要一些枚举类的字典参数,虽然可以通过swagger在线文档给前端开发,不过可以直接返回枚举的编码和字典值就可以更直观,所以在项目里怎么实现?可以通过Mybatis的一些接口,自定义枚举类的处理器实现 开发环境 JDK 1.8 SpringBoot2.2.1

    2024年02月04日
    浏览(36)
  • jmeter-BeanShell预处理器与BeanShell后置处理器的使用

    BeanShell是一个小型嵌入式Java源代码解释器,具有对象脚本语言特性,能够动态地执行标准JAVA语法,并利用在JavaScript和Perl中常见的松散类型、命令、闭包等通用脚本来对其进行拓展。 前置处理器:请求发送之前对请求参数做一些处理 后置处理器:请求发送完成之后对响应数

    2023年04月17日
    浏览(40)
  • ARM处理器架构的Thumb指令集中关于IT指令的使用

    在ARMv6T2以及ARMv7架构扩展了Thumb指令集,其中加入了 IT 指令,进一步增强了代码的紧凑性。 Thumb中有一个比较有意思的指令—— IT ,这条指令用于根据指定的条件来执行后面相继的四条指令。当然,Thumb-2中大部分算术逻辑指令都含有带条件执行的特征,不过Thumb-2是32位的。

    2024年02月07日
    浏览(49)
  • 虚拟机内存、处理器、内存、快照的修改方法(虚拟机VMware使用技巧 上 )

    1、基础设置 ①、内存 VMware有两项基础的设置 内存 和 处理器 。先说内存设置,主要是设置内存的大小。安装虚拟软件的主机内存最好不低于16G,最好32G。主机内存太小不适合运行虚拟机软件。 16G的内存可以主机和虚拟机对半分,主机留8G,虚拟机分8G。32G的内存可以按照主

    2024年02月09日
    浏览(52)
  • OpenApi接口的一次调用经历(附代码)

    去弄一个api_key:https://platform.openai.com/account/api-keys   先看所有能用的模型: 返回: babbage davinci text-davinci-edit-001 babbage-code-search-code text-similarity-babbage-001 code-davinci-edit-001 text-davinci-001 ada curie-instruct-beta babbage-code-search-text babbage-similarity whisper-1 code-search-babbage-text-001 text-curie-

    2024年02月12日
    浏览(49)
  • Spring MVC异常处理【单个控制异常处理器、全局异常处理器、自定义异常处理器】

    目录 一、单个控制器异常处理 1.1 控制器方法 1.2 编写出错页面 1.3 测试结果 二、全局异常处理 2.1 一个有异常的控制器类 2.2 全局异常处理器类 2.3 测试结果  三、自定义异常处理器 3.1 自定义异常处理器 3.2 测试结果 往期专栏文章相关导读  1. Maven系列专栏文章 2. Mybatis系列

    2024年02月16日
    浏览(43)
  • Jmeter前置处理器和后置处理器

    1. 后置处理器(Post Processor) 本质上是⼀种对sampler发出请求后接受到的响应数据进⾏处理 (后处理)的⽅法  正则表达式后置处理器 (1)引⽤名称:下⼀个请求要引⽤的参数名称,如填写title,则可⽤${title}引⽤它 (2)正则表达式: ():括起来的部分就是要提取的。 .:匹配

    2023年04月21日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包