用了那么久的 Lombok,你知道它的原理么?

序言

在写Java代码的时候,最烦写setter/getter方法,自从有了Lombok插件不用再写那些方法之后,感觉再也回不去了,那你们是否好奇过Lombok是怎么把setter/getter方法给你加上去的呢?有的同学说我们Java引入Lombok之后会污染依赖包,那我们可不可以自己写一个工具来代替Lombok呢?

知识点

  • Java编译过程
  • 了解Lombok原理
  • 了解插入式注解处理器

分析

序言提到的问题其实都是同一个问题,就是如何去获取和修改Java源代码?

要回答这个问题,我们需要回答这几个问题:

  1. Java编译器是如何解析Java源代码的?
  2. 编译器编译源代码都有哪些步骤?
  3. 我们在编译器工作的时候,怎么才能去增加内容或者是进行代码分析?

希望大家看完本文能够自己写一个简易的Lombok工具。

回答

如何解析源代码

其实从我们的代码到被编译,中间隔了一个数据结构,叫做AST(抽象树)。具体的形式,可以查看下面的图片。右边的便是AST的数据结构了。

代码编译都有哪些步骤

整个编译过程大致如下:

图片来自 openjdk

1、初始化插入注解处理器

2、解析与填充符号表过程

a.词法分析、语法分析。将源代码的字符流转变为标记集合,构造出抽象语法树。

b.填充符号表。产生符号地址和符号信息。

3、插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段。后面我会给大家带来两个此方面的实用实战例子。

4、分析与字节码生成过程

a.标注检查。对语法的静态信息检查。

b.数据流及控制流分析。对程序动态运行过程进行检查。

c.解语法糖。将简化代码编写的语法糖还原为原有的形式。

d.字节码生成。将前面各个步骤所生成的信息转化成为字节码。

我们知道了上面的理论之后,接下来我们进行实战。带着大家一起去修改AST(抽象树)。添加自己的代码。

实战

如何自己实现一个自动添加Setter/Getter的工具

首先,我们创建一个自己的注解。

@Retention(RetentionPolicy.SOURCE) // 注解只在源码中保留
@Target(ElementType.TYPE) // 用于修饰类
public @interface MySetterGetter {
}

创建一个需要生成setter/getter方法的实体类

@MySetterGetter  // 打上我们的注解
public class Test {private String wzj;
}

接下来就来看一看如何来生成我们想要的字符串。

整体代码如下:

@SupportedAnnotationTypes("com.study.practice.nameChecker.MySetterGetter")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MySetterGetterProcessor extends AbstractProcessor {// 主要是输出信息private Messager messager;private JavacTrees javacTrees;private TreeMaker treeMaker;private Names names;@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);this.messager = processingEnv.getMessager();this.javacTrees = JavacTrees.instance(processingEnv);Context context = ((JavacProcessingEnvironment)processingEnv).getContext();this.treeMaker = TreeMaker.instance(context);this.names = Names.instance(context);}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {// 拿到被注解标注的所有的类Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(MySetterGetter.class);elementsAnnotatedWith.forEach(element -> {// 得到类的抽象树结构JCTree tree = javacTrees.getTree(element);// 遍历类,对类进行修改tree.accept(new TreeTranslator(){@Overridepublic void visitClassDef(JCTree.JCClassDecl jcClassDecl) {List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();// 在抽象树中找出所有的变量for(JCTree jcTree: jcClassDecl.defs){if (jcTree.getKind().equals(Tree.Kind.VARIABLE)){JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl)jcTree;jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);}}// 对于变量进行生成方法的操作for (JCTree.JCVariableDecl jcVariableDecl : jcVariableDeclList) {messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");jcClassDecl.defs = jcClassDecl.defs.prepend(makeSetterMethodDecl(jcVariableDecl));jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));}// 生成返回对象JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewSetterMethodName(jcVariableDecl.getName()), methodType, List.nil(), parameters, List.nil(), block, null);}/*** 生成 getter 方法* @param jcVariableDecl* @return*/private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl){ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();// 生成表达式JCTree.JCReturn aReturn = treeMaker.Return(treeMaker.Ident(jcVariableDecl.getName()));statements.append(aReturn);JCTree.JCBlock block = treeMaker.Block(0, statements.toList());// 无入参// 生成返回对象JCTree.JCExpression returnType = treeMaker.Type(jcVariableDecl.getType().type);return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewGetterMethodName(jcVariableDecl.getName()), returnType, List.nil(), List.nil(), List.nil(), block, null);}/*** 拼装Setter方法名称字符串* @param name* @return*/private Name getNewSetterMethodName(Name name) {String s = name.toString();return names.fromString("set" + s.substring(0,1).toUpperCase() + s.substring(1, name.length()));}/*** 拼装 Getter 方法名称的字符串* @param name* @return*/private Name getNewGetterMethodName(Name name) {String s = name.toString();return names.fromString("get" + s.substring(0,1).toUpperCase() + s.substring(1, name.length()));}/*** 生成表达式* @param lhs* @param rhs* @return*/private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {return treeMaker.Exec(treeMaker.Assign(lhs, rhs));}
}

代码有点多,我们逐一拆解说明:

下面这是整个代码结构的脑图,后面的讲解会基于这个顺序。

a. 注解

@SupportedAnnotationTypes 表示我们需要监听的注解,比如我们之前定义的 @MySetterGetter。

@SupportedSourceVersion 表示我们想要对什么版本的Java源代码进行处理。

b. 父类

AbstractProcessor是本次的核心类,编译器在编译的时候会扫描此类的子类。其中有一个子类必须实现的核心方法 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv),此方法如果是返回为true就说明编译的那个类抽象树的结构又变化,需要重新进行词法分析和语法分析(可以查看上面提到的那个编译流程图)。如果返回的是false就说明没有变化。

c. process方法

主要的操作逻辑是:

1、拿到所有被我们MySetterGetter标注的类。

2、遍历所有的类,生成类的抽象树结构。

3、对类进行操作:

a.找到类中所有的变量。

b.对变量进行生成Set和Get方法。

4、返回 true,说明类结构变了,需要重新解析。如果是false说明没有变,不用重新解析。

d. 操作JCTree树

主要是在操作抽象树,可以查看文末附件中的文章进行学习。

e. 方法名称拼接

这一块儿和字符串拼接没啥区别,用过反射的同学应该也都清楚这个操作了。

到此为止,我们就已经介绍完了Lombok的原理。怎么样是不是很简单。接下来,就让我们把它运行起来,投入到实战之中。

f. 运行

最后来看一下如何正确的运行这个我们写的工具。

1.环境

我的系统环境是 macOs Monterey;

java版本是

openjdk version "1.8.0_302"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_302-b08)
OpenJDK 64-Bit Server VM (Temurin)(build 25.302-b08, mixed mode)

2.编译processor

在你存放 MySetterGetter 和 MySetterGetterProcessor 两个类的目录下进行编译。

javac -cp $JAVA_HOME/lib/tools.jar MySetterGetter.java MySetterGetterProcessor.java

执行成功后会出现这三个class文件。

3.声明插入式注解处理器

  • 在你的工程的resources下面创建一个包,名称为:META-INFO.services
  • 然后创建一个文件,名称为:javax.annotation.processing.Processor
  • 将你的注解处理器的地址填入,我的配置是这样的:

com.study.practice.nameChecker.MySetterGetterProcessor

4.用我们的工具去编译目标类

比如我们本次是要编译那个test.java。

它的内容再回顾一下:

@MySetterGetter  // 打上我们的注解
public class Test {private String wzj;
}

然后我们就去编译它(注意类前面的路径。这个你们得换成自己的工程目录。)

javac -processor com.study.practice.nameChecker.MySetterGetterProcessor com/study/practice/nameChecker/Test.java

执行之后如果没有修改我的代码的话会打印这几个字符串:

process 1
process 2
注: wzj has been processed
process 1

最后会生成Test.class文件。

5.成果

最后的class文件解析出来就是这个样子的。如下图所示:

看到Setter/Getter方法就说明我们已经大功告成了!是不是很简单。

到此为止,我们就学会了如何自己写一个属于自己的简易Lombok的插件了。

附件

treemarker 的介绍:

http://www.docjar.com/docs/api/com/sun/tools/javac/tree/TreeMaker.html

作者 | 王再军(曦峰)

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/510548.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Fury:一个基于JIT动态编译的高性能多语言原生序列化框架

Fury是一个基于JIT动态编译的多语言原生序列化框架&#xff0c;支持Java/Python/Golang/C等语言&#xff0c;提供全自动的对象多语言/跨语言序列化能力&#xff0c;以及相比于别的框架最高20~200倍的性能。 引言 过去十多年大数据和分布式系统蓬勃发展&#xff0c;序列化是其…

阿里云丁宇:以领先的云原生技术,激活应用构建新范式

8 月 11 日&#xff0c;2022 阿里云飞天技术峰会在深圳举行&#xff0c;会上阿里云提出云原生激活应用构建三大范式&#xff0c;并发布最新的产品与解决方案。基于分布式云容器平台 ACK One&#xff0c;实现多地域分布式系统一致管理&#xff1b;发布 ACK FinOps 解决方案&…

操作系统的“冷板凳”要坐多久?万字长文解读16年开源老兵的坚持

想知道内核研发是怎样的体验&#xff1f;操作系统的“冷板凳”得坐多久才有春天&#xff1f;本文对话龙蜥社区理事长马涛&#xff0c;畅所欲言聊开源&#xff0c;一起来看看那些开源润物细无声背后的故事以及龙蜥社区运营的道法术。 高门槛的 Linux 内核研发&#xff0c;如何支…

在阿里做前端程序员,我是这样规划的

前端程序员常问的几个问题 此文来自一次团队内的分享。我是来自大淘宝技术内容前端团队的胤涧&#xff0c;负责内容中台技术。我的习惯是每个新财年初都会进行一次分享《HOW TO BE AN EMINENT ENGINEER》&#xff0c;聊聊目前团队阵型、OKR、业务和技术大图&#xff0c;聊聊我作…

如何可视化编写和编排你的 K8s 任务

简介 K8s Job 是 Kubernetes 中的一种资源&#xff0c;用来处理短周期的 Pod&#xff0c;相当于一次性任务&#xff0c;跑完就会把 Pod 销毁&#xff0c;不会一直占用资源&#xff0c;可以节省成本&#xff0c;提高资源利用率。 阿里任务调度 SchedulerX 和云原生结合&#x…

前端智能化实践——可微编程

什么是可微编程 通过动画、动效增加 UI 表现力&#xff0c;作为前端或多或少都做过。这里以弹性阻尼动画的函数为例&#xff1a; 函数在 时是效果最好的。最终&#xff0c;实现成 JavaScript 代码&#xff1a; function damping(x, max) {let y Math.abs(x);// 下面的参数都是…

解析 RocketMQ 业务消息——“事务消息”

引言&#xff1a;在分布式系统调用场景中存在这样一个通用问题&#xff0c;即在执行一个核心业务逻辑的同时&#xff0c;还需要调用多个下游做业务处理&#xff0c;而且要求多个下游业务和当前核心业务必须同时成功或者同时失败&#xff0c;进而避免部分成功和失败的不一致情况…

模型代码联动难? BizWorks 来助力

业务模型设计和沉淀是企业数字化转型过程中非常重要的一个环节, 日趋复杂的业务场景和协作模式给建模的有效性以及模型作为业务资产如何持续发挥价值带来了新的挑战: 设计完成的业务模型是否被合理实现了?经过数月、半年、1年迭代后&#xff0c;模型设计还能否对业务系统的演…

EasyNLP 集成 K-BERT 算法,借助知识图谱实现更优 Finetune

导读 知识图谱&#xff08;Knowledge Graph&#xff09;的概念⾸次出现2012年&#xff0c;由Google提出&#xff0c;它作为⼀种⼤规模语义⽹络&#xff0c; 准确地描述了实体以及实体之间的关系。知识图谱最早应⽤于搜索引擎&#xff0c;⽤于准备返回⽤户所需的知识。随着预训…

一种关于低代码平台(LCDP)建设实践与设计思路

背景 负责菜鸟商业中心CRM系统开发已经有1年多时间&#xff0c;过程中发现有一个痛点&#xff1a;业务线特别多&#xff0c;每个业务线对同一个页面都有个性化布局和不同的字段需求&#xff0c;而我所在的团队就3个人&#xff0c;在资源有限的情况下如何支撑好呢&#xff1f;刚…

Redis 数据类型 list 以及使用场景

数据存储需求&#xff1a;存储多个数据&#xff0c;并对数据进入存储空间的顺序进行区分 需要的存储结构&#xff1a;一个存储空间保存多个数据&#xff0c;且通过数据可以体现进入顺序 list类型&#xff1a;保存多个数据&#xff0c;底层使用双向链表存储结构实现 list 类型数…

TairSearch:加速多列索引查询

互联网及传统行业应用服务的关键数据一般存储在MySQL这类的关系型数据库中。如需缓解数据库访问压力&#xff0c;可引入Redis等缓存系统承担热数据的查询&#xff0c;以此提升查询效能。然而业务场景如果是在数据库上做随意多列组合索引查询或者like模糊匹配查询&#xff0c;使…

如何在 Anolis 8上部署 Nydus 镜像加速方案?

在上一篇文章中详细介绍Anolis OS 是首个原生支持镜像加速 Linux 内核&#xff0c;Nydus 镜像加速服务重新优化了现有的 OCIv1 容器镜像格式&#xff0c;重新定义镜像的文件系统&#xff0c;数据与元数据分离&#xff0c;实现按需加载&#xff0c;本文作为使用 Nydus 的教程将详…

机器学习访存密集计算编译优化框架AStitch,大幅提升任务执行效率

近日&#xff0c;关于机器学习访存密集计算编译优化框架的论文《AStitch: Enabling A New Multi-Dimensional Optimization Space for Memory-Intensive ML Training and Inference on Modern SIMT Architectures》被系统领域顶会ASPLOS 2022接收。 AStitch通过编译优化的手段来…

微前端架构的几种技术选型

背景 随着SPA大规模的应用&#xff0c;紧接着就带来一个新问题&#xff1a;一个规模化应用需要拆分。 一方面功能快速增加导致打包时间成比例上升&#xff0c;而紧急发布时要求是越短越好&#xff0c;这是矛盾的。另一方面当一个代码库集成了所有功能时&#xff0c;日常协作绝…

真正的 HTAP 对用户和开发者意味着什么?

数据库的全称是 DBMS&#xff08;Database Management System&#xff09;&#xff0c;早期是不区分 OLTP 与 OLAP 的&#xff0c;E.F.Codd 在 1970 年就提出了关系模型&#xff0c;Jim Gray 在 1976 年提出了事务模型。随着数据库的应用场景越来越丰富&#xff0c;单一数据库的…

const常见用法

const用法主要是防止定义的对象再次被修改,定义对象变量时要初始化变量 下面我就介绍一下几种常见的用法 1.用于定义常量变量,这样这个变量在后面就不可以再被修改 const int Val 10; //Val 20; //错误,不可被修改 2. 保护传参时参数不被修改,如果使用引用传递参数或按地址传…

微服务治理热门技术揭秘:无损上线

为什么有了无损下线&#xff0c;还需要无损上线&#xff1f;无损上线可以解决哪些问题&#xff1f; 本篇文章将一一回答这些问题。 无损上线功能不得不说是一个客户打磨出来的功能我们将从一次发布问题的排查与解决的过程说起。 背景 阿里云内部某应用中心服务在发布过程中出…

深度强化学习技术概述

深度强化学习介绍 强化学习主要用来学习一种最大化智能体与环境交互获得的长期奖惩值的策略&#xff0c;其常用来处理状态空间和动作空间小的任务&#xff0c;在如今大数据和深度学习快速发展的时代下&#xff0c;针对传统强化学习无法解决高维数据输入的问题&#xff0c;2013…

大屏小程序探索实践 | Cube 技术解读

所谓大屏小程序&#xff0c;是以 Cube 小程序技术栈 为载体&#xff0c;运行在智能电视或智能机顶盒等设备上的一种小程序形态。这些设备的主要特点是&#xff1a; 以 Android 系统为主&#xff0c;系统版本普遍较低&#xff0c;有些设备依然停留在 Android 4.2&#xff0c;An…