Java核心:注解处理器

Java提供了一个javac -processor命令支持处理标注有特定注解的类,来生成新的源文件,并对新生成的源文件重复执行。执行的命令大概是这样的:

javac -XprintRounds -processor com.keyniu.anno.processor.ToStringProcessor com.keyniu.anno.processor.Point

本文的目标是用一个案例来讲解注解处理器的使用,我们会定义一个@ToString注解,创建注解处理器,为所有标注了@ToString注解的类生成toString工具方法。

这里需要特别说明的是javac -processor只支持生成新的文件,无法在原来的文件里做修改。

1. 定义ToString注解

首先我们需要定义一个注解,用来标注后续要生成toString方法的类。@ToString的逻辑很简单,这里我们只把它定义为一个标记注解。

package com.keyniu.anno.processor;import java.lang.annotation.*;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ToString {
}

定义@ToString注解之后,我们就可以把它用在想要自动生成toString方法的类上,比如我们有一个Point类的定义,我们希望为Point类生成toString方法,可以在Point上添加@ToString注解

package com.keyniu.anno.processor;@ToString
public class Point {private int x;private int y;public int getX(Point this) {return x;}public int getY() {return y;}
}

2. 创建注解处理器

要想生成代码,我们还需要定义注解处理器,来处理代码的生成。注解处理器需要继承AbstractProcessor类,通过注解能指定支持的注解、代码版本号。下面的代码展示了整个处理过程,我们来解释一下运行流程:

  1. 入参annotations是当前注解处理器支持的注解类型,@SupportedAnnotationTypes可以指定通配符,所以annotaions可以有多个注解类,不过这个案例中,注解只有@ToString
  2. 通过RoundEnvironment.getElementsAnnotatedWith查找标注了@ToString的Element,它有3个子类,TypeElement(类、接口)、VariableElement(字段、参数)、ExecutableElement(方法、构造器)
  3. 这里我们只关心的类类型(TypeElement)
  4. 使用processingEnv.getFiler().createSourceFile创建生成的类文件,此处我们要生成的是com.keyniu.anno.processor.StringUtils类
  5. 创建文件输出流PrintWriter,用于后续写入.java文件
  6. 后续要做的就是通过字符串拼接,生成.java文件的内容了,先定义包,设置import,然后定义类,最后是定义方法的代码,这个过程中可以使用TypeElement的元数据
  7. PrintWriter关闭后,新的.java文件就会倍生成,新生成的类,会重新走一边注解处理的过程
package com.keyniu.anno.processor;import java.io.PrintWriter;
import java.util.Iterator;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.JavaFileObject;@SupportedAnnotationTypes({"com.keyniu.anno.processor.ToString"})
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class ToStringProcessor extends AbstractProcessor {public ToStringProcessor() {}public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {try {Set<? extends Element> es = roundEnv.getElementsAnnotatedWith(ToString.class);  // 步骤2,寻找标准了@ToString的所有ElementIterator var4 = es.iterator();while(var4.hasNext()) {Element e = (Element)var4.next();if (e instanceof TypeElement te) { // 步骤3,我们只关心注解了@ToString的TypeElementJavaFileObject jfo = this.processingEnv.getFiler().createSourceFile("com.keyniu.anno.processor.StringUtils", new Element[0]); // 步骤4PrintWriter out = new PrintWriter(jfo.openWriter()); // 步骤5try {this.printClass(out);  // 步骤6this.printMethod(te, out);this.printClassSuffix(out);} catch (Throwable var12) {try {out.close();} catch (Throwable var11) {var12.addSuppressed(var11);}throw var12;}out.close();}}return false;} catch (Exception var13) {var13.printStackTrace();return false;}}private void printClass(PrintWriter out) {out.println("package com.keyniu.anno.processor;");out.println("");out.println("import java.lang.StringBuilder;");out.println("");out.println("public class StringUtils {");}private void printClassSuffix(PrintWriter out) {out.println("}");}private void printMethod(TypeElement te, PrintWriter out) {String indent = "    ";StringBuilder methodCode = new StringBuilder();methodCode.append(indent + "public static java.lang.String toString(" + te.getQualifiedName() + " i) {");methodCode.append("\n");methodCode.append(indent + indent + "StringBuilder sb = new StringBuilder();");methodCode.append("\n");Iterator var5 = te.getEnclosedElements().iterator();while(var5.hasNext()) {Element e = (Element)var5.next();if (e instanceof VariableElement ve) {String field = ve.getSimpleName().toString();methodCode.append(indent + indent + "sb.append(\"" + field + ":\");").append("\n");methodCode.append(indent + indent + "sb.append(i.get" + field.substring(0, 1).toUpperCase() + field.substring(1) + "());").append("\n");}}methodCode.append(indent + indent + "return sb.toString();\n");methodCode.append(indent + "}");out.println(methodCode);}
}

3. 调用注解处理器

在注解类(ToString)、注解处理器(ToStringProcessor)和使用注解的类(Point)都定义完成后我们就可以开始调用javac -processor类。首先要做的是编译ToString和ToStringProcessor类

javac com/keyniu/anno/processor/ToString.java
javac com/keyniu/anno/processor/ToStringProcessor.java

然后就可以使用-processor引用ToStringProcessor类了,当然你要保证ToStringProcessor类在classpath下可访问

D:\Workspace\HelloJava17\src\main\java>javac -XprintRounds -processor com.keyniu.anno.processor.ToStringProcessor com.keyniu.anno.processor.Point

执行结束后,你会看到在D:\Workspace\HelloJava17\src\main\java\com\keyniu\anno\processor下新生成了一个StringUtils类,生成的代码如下

package com.keyniu.anno.processor;import java.lang.StringBuilder;public class StringUtils {public static java.lang.String toString(com.keyniu.anno.processor.Point i) {StringBuilder sb = new StringBuilder();sb.append("x:");sb.append(i.getX());sb.append("y:");sb.append(i.getY());return sb.toString();}
}

4. 提供Maven支持

应该承认javac -processor确实能用了,但是为编译过程额外添加了一个步骤,带来了额外的负担,而且生成的代码和我们用户代码混杂在一起了。通过Maven的maven-compiler-plugin插件,能让这个过程自动化,并为生成的代码提供单独的目录。为了让这个过程可行,我们现在将项目拆分为两个,anno-processing提供ToString定义、ToStringProcessor注解处理器定义

<groupId>com.keyniu</groupId>
<artifactId>anno-processing</artifactId>
<version>1.0-SNAPSHOT</version>

在客户端工程,提供Point定义,引用anno-processing的依赖, GAV和依赖定义如下

<groupId>com.randy.graalvm</groupId>
<artifactId>swing</artifactId>
<version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>com.keyniu</groupId><artifactId>anno-processing</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>

紧接着要做的是在swing项目中,添加maven-compiler-plugin插件,定义生成文件保存的目录(generatedSourcesDirectory),以及注解处理器(annotationProcessor)

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.12.1</version><configuration><source>17</source><target>17</target><encoding>UTF-8</encoding><generatedSourcesDirectory>${project.build.directory}/generated-sources/</generatedSourcesDirectory><annotationProcessors><annotationProcessor>com.keyniu.anno.processor.ToStringProcessor</annotationProcessor></annotationProcessors></configuration></plugin></plugins>
</build>

在这些配置都完成后,就可以正常的通过mvn package编译打包了,运行后能看到target目录下多了一个generated-sources,并且在classes文件夹下包含了StringUtils编译后的.class文件

事情做到这一步,应该说我们定义的ToStringProcessor和ToString已经能满足特定场景下的时候了,不过它并不支持修改,自能新生成一个类来扩展现有类的能力,仍然显得不那么完美。

下一篇我们会讲解lombok的实现原理,怎么在类加载时使用字节码操作类库动态的修改Class的实现。

A. 参考资料

  1. Java Annotation Processing and Creating a Builder | Baeldung

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

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

相关文章

基于微信小程序的在电影线订票小程序+web管理 uniapp,vue,ssm

基于微信小程序的在电影线订票小程序web管理 uniapp&#xff0c;vue&#xff0c;ssm 相关技术 javassmuniapp微信开发者工具hbuildervueelementui前后端分离 -mysql

PointCloudLib 点云半径滤波实现 C++版本

0.展示效果 滤波之前 1.算法原理 半径滤波原理非常直观,主要用于平滑三维点云数据并去除离群点。 设定搜索半径:首先,为每个点设定一个搜索半径r。这个半径定义了该点周围的一个球形区域。计算邻域点数:接着,计算每个点在其搜索半径r内的邻近点的数量。判断与过滤:根据…

xcode按下delete键不能删除不能使用,解决办法

有可能是按键冲突导致的问题&#xff0c;就是你不小心把delete键绑定了不同的快捷键&#xff0c;所以需要恢复所有的偏好设置和快捷键才可以&#xff0c;我这里就是这样的提示内容&#xff0c;在xcode中按delete键完全无效&#xff1a; 而且还会报红色提示&#xff1a;意思是不…

KingbaseES数据库union的用法

数据库版本&#xff1a;KingbaseES V008R006C008B0014 文章目录如下 1. union的概念 2. union的语法 3. union的用法 3.1. 去重&#xff08;union&#xff09; 3.2. 不去重&#xff08;union all&#xff09; 3.3. 聚合运算 3.4. 异常案例 1. union的概念 UNION 是结构…

冷冻式压缩空气干燥机常见几种系统原理图

冷冻式压缩空气干燥机 我们以两种典型的设计流程图为例 1.干式蒸发型&#xff0c;这类冷干机是我们最为常见的设计型式。下图为deltech公司的典型流程图 此类设备各家设计不同的最大区别基本就是在换热器的结构型式上有比较大的区别。换热器主要有&#xff1a;管壳式、铝板换、…

typescript 配置精讲 | moduleResolution

大家好&#xff0c;我是17。 moduleResolution 是 typescript 模块配置中最重要的一个配置&#xff0c;所以 17 单拿出来讲一下。如果你去看文档还是挺复杂的&#xff0c;但如果不去深究细节&#xff0c;只想知道如何配置还是很简单的。3 分钟就能学会。 moduleResolution 的…

STM32无源蜂鸣器播放音乐

开发板&#xff1a;野火霸天虎V2 单片机&#xff1a;STM32F407ZGT6 开发软件&#xff1a;MDKSTM32CubeMX 文章目录 前言一、找一篇音乐的简谱二、确定音调三、确定节拍四、使用STM32CubeMX生成初始化代码五、代码分析 前言 本实验使用的是低电平触发的无源蜂鸣器 无源蜂鸣器是…

【模拟面试问答】深入解析力扣163题:缺失的区间(线性扫描与双指针法详解)

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

华为OD机试 - 寻找最富裕的小家庭(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

C语言对一阶指针 二阶指针的本质理解

代码&#xff1a; #include <stdio.h>char a 2; char* p &a; char** d &p;int main(){printf("a -> %d, &a -> %p\n", a, &a);printf("*p -> %d, p -> %p, &p -> %p\n", *p, p, &p);printf(&qu…

【JavaEE初阶】网络初识|局域网和广域网|交换机和路由器|IP地址|端口号

&#x1f4a1;推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击跳转到网站】 关键概念 1.局域网LAN和广域网WAN &#xff08;1&#xff09;局域⽹&#xff0c;即Local Area Network&#xff0…

嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻嘻

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

【代码随想录】动态规划经典题

前言 更详细的在大佬的代码随想录 (programmercarl.com) 本系列仅是简洁版笔记&#xff0c;为了之后方便观看 做题步骤 含义公式初始化顺序检查 确定dp数组以及下标的含义递推公式dp数组如何初始化遍历顺序打印dp数组&#xff08;看哪里有问题&#xff09; 斐波那契数 c…

乐理学习-音及音名

1. 我觉得练习题很重要。我要得到一个反馈 所以我想没学习完书中的一节就要把练习题做下来&#xff0c;虽然慢点也可以。 2. 做个小计划。 今天计算了一下学完《基本乐理-李重光》如果每天3张。也要80天干完&#xff0c;希望能有一天可以学习7张的速度的时候。 3. 练习记录…

STM32H7系统窗口看门狗 (WWDG)应用方法介绍

目录 概述 1 认识窗口看门狗 (WWDG) 1.1 窗口看门狗定义 1.2 WWDG 主要特性 2 WWDG 功能说明 2.1 WWDG框图 2.2 WWDG 内部信号 2.3 控制递减计数器 2.4 看门狗中断高级特性 2.5 如何设置看门狗超时 3 WWDG 寄存器 3.1 控制寄存器 (WWDG_CR) 3.2 配置寄存器 (W…

MicroLED:苹果对知识产权的影响

Yole的洞察揭示&#xff0c;MicroLED IP在经历了七年的爆炸式增长后&#xff0c;已然屹立于行业之巅。苹果公司&#xff0c;作为微LED领域的先行者&#xff0c;早在2014年便敏锐地捕捉到Luxvue这家初创公司的潜力&#xff0c;将其纳入麾下&#xff0c;引发了业界的广泛关注。然…

【Springboot系列】SpringBoot 中的日志如何工作的,看完这一篇就够了

文章目录 强烈推荐引言Spring Boot 中的日志是怎么工作日志框架选择配置文件日志级别自定义日志配置集成第三方日志库实时监控和日志管理 Log4j2工作原理分析1. 核心组件2. 配置文件3. Logger的继承和层次结构4. 日志事件处理流程5. 异步日志 总结强烈推荐专栏集锦写在最后 强烈…

每日练习之矩阵乘法——斐波那契公约数

斐波那契公约数 题目描述 运行代码 #include <iostream> #include <vector> using namespace std; const long long mod 1000000007;// 矩阵乘法函数 vector<vector<long long>> matrixMultiply(const vector<vector<long long>>& …

移动端特效

一&#xff0c;触屏事件 1.触屏touch事件 2.触摸事件对象 如果侦听的是一个DOM元素&#xff0c;他俩是一样的 如果手指离开了&#xff0c;不能侦听到前两者&#xff0c;能侦听到第三个 我们一般都是触摸元素&#xff0c;所以一般都是第二个比较常用 第一个&#xff1a;屏幕…

Leetcode 力扣92. 反转链表 II (抖音号:708231408)

给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4 输出&#xff1a;[1,4,3,2…