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

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

3天内不再提示

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

芋道源码 来源:码猿技术专栏 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实现的插件在功能上有很大的发挥空间。






审核编辑:刘清

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

    关注

    67

    文章

    16655

    浏览量

    219884
  • JAVA
    +关注

    关注

    19

    文章

    2654

    浏览量

    102410
  • SPI
    SPI
    +关注

    关注

    17

    文章

    1482

    浏览量

    88696
  • RPC
    RPC
    +关注

    关注

    0

    文章

    87

    浏览量

    11337
收藏 人收藏

    评论

    相关推荐

    #微处理器与嵌入系统设计 ARM微处理器简介

    电子技术那些事儿
    发布于 :2022年10月21日 21:46:17

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

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

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

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

    Cortex-M4处理器使用的流水线具有哪些特征

    处理器使用的流水线具有哪些特征?
    发表于 10-15 07:56

    嵌入处理器是什么

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

    HarmonyOS注解的使用方法分享

    注解处理器新建
    发表于 03-28 14:04

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

    处理器使用哪个管理程序吗?
    发表于 03-16 07:07

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

    记录系统的设计方案, 利用ARM 微处理器实现了模拟信号较完整的故障波形记录和开关信号的事件顺序记录(SOE) 。对L PC2106ARM 系列处理器、ADS787
    发表于 06-02 17:11 57次下载
    基于ARM 微<b>处理器</b>的故障<b>记录</b>系统

    嵌入处理器

    处理器是嵌入系统的核心,是控制、辅助系统运行的硬件单元。范围极其广阔,从最初的4位处理器,目前仍在大规模应用的8位单片机,到最新的受到广泛青睐的32位,64位嵌入CPU。   自
    发表于 10-20 17:21 1次下载
    嵌入<b>式</b><b>处理器</b>

    什么是插入封装

    插入封装 引脚
    发表于 03-04 11:02 2480次阅读

    Spring Web MVC注解

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

    Springboot常用注解合集

    注解,此注解是个组合注解,包括了`@SpringBootConfiguration`、`@EnableAutoConfiguration`和`@ComponentScan`
    的头像 发表于 04-07 14:27 299次阅读
    Springboot常用<b>注解</b>合集

    嵌入处理器有哪些种类 目前主流的嵌入处理器架构有哪些

    处理器是专门设计用于嵌入系统的微处理器,它们通常具有低功耗、小尺寸和多种集成功能的特点。以下是一些常见的嵌入处理器种类
    发表于 07-24 14:57 1603次阅读