0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

插入式注解处理器使用记录

jf_ro2CN3Fa 来源:码猿技术专栏 2023-01-31 16:47 次阅读

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

了解过lombok底层原理的都知道其使用的就是的插入式注解,那么今天笔者就以真实场景演示一下插入式注解的使用。

需求

我们为公司提供了一套通用的JAVA基础组件包,组件包内有不同的模块,比如熔断模块、负载均模块、rpc模块等等,这些模块均会被打成jar包,然后发布到公司的内部代码仓库中,供其他人引入使用。

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

25f0eaae-8bee-11ed-bfe3-dac502259ad0.png

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

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

问题

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

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

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

@TrisceliVersion
publicstaticfinalStringversion="";

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

@TrisceliVersion
publicstaticfinalStringversion="1.0.31-SNAPSHOT";

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

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

解决

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

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

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

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

25fff436-8bee-11ed-bfe3-dac502259ad0.png

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

自定义一个插入式注解处理器也很简单,首先要将自己的注解定义出来:

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

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

/**
*{@linkAbstractProcessor}就属于PluggableAnnotationProcessingAPI
*/
publicclassTrisceliVersionProcessorextendsAbstractProcessor{

privateJavacTreesjavacTrees;
privateTreeMakertreeMaker;
privateProcessingEnvironmentprocessingEnv;

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


@Override
publicSourceVersiongetSupportedSourceVersion(){
returnSourceVersion.latest();
}

@Override
publicSetgetSupportedAnnotationTypes(){
HashSetset=newHashSet<>();
set.add(TrisceliVersion.class.getName());//支持解析的注解
returnset;
}

@Override
publicbooleanprocess(Setannotations,RoundEnvironmentroundEnv){
for(TypeElementt:annotations){
for(Elemente:roundEnv.getElementsAnnotatedWith(t)){//获取到给定注解的element(element可以是一个类、方法、包等)
//JCVariableDecl为字段/变量定义语法树节点
JCTree.JCVariableDecljcv=(JCTree.JCVariableDecl)javacTrees.getTree(e);
StringvarType=jcv.vartype.type.toString();
if(!"java.lang.String".equals(varType)){//限定变量类型必须是String类型,否则抛异常
printErrorMessage(e,"Type'"+varType+"'"+"isnotsupport.");
}
jcv.init=treeMaker.Literal(getVersion());//给这个字段赋值,也就是getVersion的返回值
}
}
returntrue;
}

/**
*利用processingEnv内的Messager对象输出一些日志
*
*@parameelement
*@parammerrormessage
*/
privatevoidprintErrorMessage(Elemente,Stringm){
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,m,e);
}

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

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

260d4104-8bee-11ed-bfe3-dac502259ad0.png

测试

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

261da74c-8bee-11ed-bfe3-dac502259ad0.png

这是Test类:

262e5bbe-8bee-11ed-bfe3-dac502259ad0.png

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

264380a2-8bee-11ed-bfe3-dac502259ad0.png

这只是插入式注解处理器 功能的冰山一角,既然它可以通过修改抽象语法树来控制生成的字节码,那么自然就有人能充分利用其特性来实现一些很酷的插件,比如lombok,我们再也不用写诸如set/get这种模板式的代码了,只要我们足够有创意,就可以让基于这一套API实现的插件在功能上有很大的发挥空间。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 处理器
    +关注

    关注

    68

    文章

    19135

    浏览量

    228954
  • JAVA
    +关注

    关注

    19

    文章

    2954

    浏览量

    104511
  • SPI
    SPI
    +关注

    关注

    17

    文章

    1695

    浏览量

    91270
  • RPC
    RPC
    +关注

    关注

    0

    文章

    111

    浏览量

    11502

原文标题:项目终于用上了插入式注解,真香!

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    求推荐蓝牙芯片!价格低于15元的,可以用作处理器使

    我想实现蓝牙接收数据,有数据处理器,有音频处理器,又支持手机升级蓝牙固件。有这样的芯片吗?能想到的方案有:1,蓝牙芯片(可以手机升级固件,可以做处理器使用)+音频解码+功放:那么就要求蓝牙芯片本身
    发表于 07-11 22:54

    写入EEPROM的数据是否可供处理器使用?

    写入EEPROM的数据是否可供处理器使用?如果我写3个不同的代码到EEPROM,处理器可以根据用户需求选择3个1吗? 以上来自于百度翻译 以下为原文Is the data written
    发表于 05-31 14:43

    嵌入处理器是什么

      嵌入处理器是嵌入系统的核心,是控制、辅助系统运行的硬件单元。范围极其广阔,从最初的4位处理器,目前仍在大规模应用的8位单片机,到最新的受到广泛青睐的32位,64位嵌入
    发表于 10-27 07:24

    嵌入处理器是什么

      嵌入处理器是嵌入系统的核心,是控制、辅助系统运行的硬件单元。范围极其广阔,从最初的4位处理器,目前仍在大规模应用的8位单片机,到最新的受到广泛青睐的32位,64位嵌入
    发表于 10-28 08:56

    HarmonyOS注解的使用方法分享

    ({ElementType.TYPE})//用于 接口、类、枚举@Retention(RetentionPolicy.CLASS)//编译阶段public @interface CheckGetter {}2、编写注解处理器新建
    发表于 03-28 14:04

    可以为S32E2处理器使用哪个管理程序吗?

    我能知道我可以为 S32E2 处理器使用哪个管理程序吗?
    发表于 03-16 07:07

    工控处理器和嵌入处理器谁更优?

    工控处理器和嵌入处理器谁更优?   工业计算机和所谓的嵌入相比,工业计算机
    发表于 02-11 14:50 552次阅读

    什么是插入封装

    什么是插入封装 引脚插入封装(Through-Hole Mount)。此封装形式有引脚出来,并将引脚直接插入印刷电路板(PWB)中,再
    发表于 03-04 11:02 3586次阅读

    嵌入处理器选型

    嵌入处理器分类 处理器造型需考虑的因素 多处理器在复杂系统中的应用
    发表于 02-28 11:57 64次下载
    嵌入<b class='flag-5'>式</b><b class='flag-5'>处理器</b>选型

    基于ARM 微处理器的故障记录系统

    本文给出了在普通数据采集基础上的故障记录系统的设计方案, 利用ARM 微处理器实现了模拟信号较完整的故障波形记录和开关信号的事件顺序记录(SOE) 。对L PC2106ARM 系列
    发表于 06-02 17:11 57次下载
    基于ARM 微<b class='flag-5'>处理器</b>的故障<b class='flag-5'>记录</b>系统

    Spring Web MVC注解

    RequestMapping注解的主要用途是将Web请求与请求处理类中的方法进行映射。Spring MVC和Spring WebFlux都通过`RquestMappingHandlerMapping`和`RequestMappingHndlerAdapter`两个类来提供
    的头像 发表于 04-07 11:32 575次阅读
    Spring Web MVC<b class='flag-5'>注解</b>

    Springboot常用注解合集

    前几章,在系统启动类里面,都加入了此启动注解,此注解是个组合注解,包括了`@SpringBootConfiguration`、`@EnableAutoConfiguration`和`@ComponentScan`
    的头像 发表于 04-07 14:27 703次阅读
    Springboot常用<b class='flag-5'>注解</b>合集

    fido5100和fido5200 REM交换芯片与主机和网络处理器使

    电子发烧友网站提供《fido5100和fido5200 REM交换芯片与主机和网络处理器使用.pdf》资料免费下载
    发表于 11-22 10:38 0次下载
    fido5100和fido5200 REM交换芯片与主机和网络<b class='flag-5'>处理器使</b>用

    springmvc常用5种注解

    SpringMVC是一种基于Java的Web框架,使用注解可以更加方便灵活地开发和管理控制器,实现请求的映射和处理。在SpringMVC中,有许多常用的注解,本文将详细介绍其中的五种注解
    的头像 发表于 11-22 16:51 786次阅读

    什么是插入无线传感器?作用是什么?

    。通过将这些数据传输到无线网络,插入无线传感器允许实时监测和分析各种应用领域的信息。 插入无线传感器通常由以下几个主要组件组成:传感器、微处理器
    的头像 发表于 12-12 15:04 596次阅读