您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

公司项目终于用上了插入式注解,真香!

时间:2022-12-16 17:35:55  来源:今日头条  作者:王路飞学Java

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

需求

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

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


 

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

问题

需求似乎很简单,但真要获取自身的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类型的,也就是说这个注解只在编译期有效,它甚至不会被编入class文件,所以lombok无疑是第一种解析方式,那用什么方式可以在编译期就让注解被解析到并执行我们的解析代码呢?答案就是定义插入式注解处理器(通过JSR-269提案定义的Pluggable Annotation Processing API实现)

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


 

也就是说插入式注解处理器可以帮助我们在编译期修改抽象语法树(AST)!所以现在我们只需要自定义一个这样的处理器,然后其内部拿到jar版本信息(因为是编译期,可以找到源码的path,源码里随便搞个文件存放版本号,然后用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 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.services:


 

测试

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


 

这是Test类:


 

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


 

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

 

原文:https://mp.weixin.qq.com/s/_VzwbsYUgbY53bc8d9AJEA

 

如果感觉本文对你有帮助,点赞关注支持一下



Tags:插入式注解   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
公司项目终于用上了插入式注解,真香!
了解过lombok底层原理的都知道其使用的就是的插入式注解,那么今天笔者就以真实场景演示一下插入式注解的使用。需求我们为公司提供了一套通用的java基础组件包,组件包内有不同...【详细内容】
2022-12-16  Search: 插入式注解  点击:(271)  评论:(0)  加入收藏
▌简易百科推荐
Netflix 是如何管理 2.38 亿会员的
作者 | Surabhi Diwan译者 | 明知山策划 | TinaNetflix 高级软件工程师 Surabhi Diwan 在 2023 年旧金山 QCon 大会上发表了题为管理 Netflix 的 2.38 亿会员 的演讲。她在...【详细内容】
2024-04-08    InfoQ  Tags:Netflix   点击:(2)  评论:(0)  加入收藏
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(7)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(13)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(9)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(5)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(11)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(9)  评论:(0)  加入收藏
为什么都说 HashMap 是线程不安全的?
做Java开发的人,应该都用过 HashMap 这种集合。今天就和大家来聊聊,为什么 HashMap 是线程不安全的。1.HashMap 数据结构简单来说,HashMap 基于哈希表实现。它使用键的哈希码来...【详细内容】
2024-03-22  Java技术指北  微信公众号  Tags:HashMap   点击:(11)  评论:(0)  加入收藏
如何从头开始编写LoRA代码,这有一份教程
选自 lightning.ai作者:Sebastian Raschka机器之心编译编辑:陈萍作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大...【详细内容】
2024-03-21  机器之心Pro    Tags:LoRA   点击:(12)  评论:(0)  加入收藏
这样搭建日志中心,传统的ELK就扔了吧!
最近客户有个新需求,就是想查看网站的访问情况。由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的...【详细内容】
2024-03-20  dbaplus社群    Tags:日志   点击:(4)  评论:(0)  加入收藏
相关文章
    无相关信息
站内最新
站内热门
站内头条