Java核心:注解处理器

news2024/11/16 16:49:12

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的所有Element
            Iterator var4 = es.iterator();

            while(var4.hasNext()) {
                Element e = (Element)var4.next();
                if (e instanceof TypeElement te) { // 步骤3,我们只关心注解了@ToString的TypeElement
                    JavaFileObject jfo = this.processingEnv.getFiler().createSourceFile("com.keyniu.anno.processor.StringUtils", new Element[0]); // 步骤4
                    PrintWriter out = new PrintWriter(jfo.openWriter()); // 步骤5

                    try {
                        this.printClass(out);  // 步骤6
                        this.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.coloradmin.cn/o/1698813.html

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

相关文章

LeetCode热题100—链表(一)

160.相交链表 题目 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#x…

头歌结构化分析方法-数据流图

第1关&#xff1a;数据流图-画出外部实体 第2关&#xff1a;数据流图-画出加工 第3关&#xff1a;数据流图-画出数据存储 第4关&#xff1a;数据流图-画出数据流 第5关&#xff1a;数据流图-机票预定系统

奥维互动地图奥维彩色图源

彩色图源(不足&#xff0c;更精细放大) 等位线3D模式 中科星 谷歌2024(不足没以前高清)

神经网络与深度学习-简要入门

参考引用 神经网络与深度学习 1. 绪论 从根源来讲&#xff0c;深度学习是机器学习的一个分支&#xff0c;是指一类问题以及解决这类问题的方法 深度学习问题是一个机器学习问题&#xff0c;指从有限样例中通过算法总结出一般性的规律&#xff0c;并可以应用到新的未知数据上深…

vi和vim编辑器

目录 1 vi和vim的基本介绍 2 vi和vim常用的三种模式 1&#xff09;正常模式 2&#xff09;插入模式 3&#xff09;命令行模式 3 vim快捷键 1&#xff09;普通模式下&#xff1a; 2&#xff09;输入模式 3&#xff09;命令行模式 4&#xff09;可视模式 1 vi和vim的基本…

【机器学习与大模型】开源大模型和闭源大模型:技术发展与社会责任的平衡点

目录 &#x1f4a1;引言✈️✈️一&#xff0c;开源大模型的优势与劣势✈️✈️1.1 优势&#xff1a;✈️✈️1.2 挑战和劣势&#xff1a; &#x1f680;&#x1f680;2. 闭源大模型的优势与劣势&#x1f680;&#x1f680;2.1 优势&#xff1a;&#x1f680;&#x1f680;2.2 …

代码随想录算法训练营第四天| 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点 、 面试题 02.07. 链表相交、142.环形链表II

24. 两两交换链表中的节点 题目链接&#xff1a; 24. 两两交换链表中的节点 文档讲解&#xff1a;代码随想录 状态&#xff1a;没做出来&#xff0c;没有正确更新头节点&#xff0c;因为head和cur共享引用&#xff0c;会随着cur的移动&#xff0c;丢失之前存放的节点 错误代码&…

6.定时器分时复用测量占空比

1.CUBEMAX配置 测量PA6&#xff0c;PA7输出的占空比&#xff0c;只需要把主要的配置&#xff0c;配置为A6口就行&#xff0c;A7口黄色表示配置不正确&#xff0c;不用管。 2.软件代码 TIME.c中找到TIM3的初始化&#xff0c;在后面初始化A7口 void MX_TIM3_Init_PA7(void) {/*…

JVM之【类加载机制】

一、类加载过程 1. 加载&#xff08;Loading&#xff09; 工作内容&#xff1a; 通过类的全限定名来获取定义此类的二进制字节流&#xff1a; JVM首先会调用类加载器的findClass方法来找到类文件的路径&#xff0c;通常从文件系统、JAR包、网络、数据库等来源获取类文件。 将…

揭秘Markdown:轻松掌握基础语法,让你的写作更高效、优雅!

文章目录 前言1.标题1.1 使用 和 - 标记一级和二级标题1.2 使用 # 号标记 2.段落格式2.1 字体2.2 分割线2.3 删除线2.4 下划线2.5 脚注 3.列表3.1 无序列表3.2 有序列表3.3 列表嵌套 4.区块4.1 区块中使用列表4.2 列表中使用区块 5.代码代码区块 6.链接7.图片8.表格9.高级技巧…

C#对文件进行批量重命名或者对某个单独的文件进行改名

目录 一、FolderBrowserDialog 二、OpenFileDialog 三、Path 四、ui设计 五、代码部分 一、FolderBrowserDialog FolderBrowserDialog是一个用于选择文件夹的对话框控件&#xff0c;可以在windows Forms应用程序中使用。使用它可以让用户选择一个文件夹&#xff0c;并返…

闲话 .NET(6):.NET Core 各个版本的特性

前言 之前我们聊了一下 .NET Core 有哪些优势&#xff0c;.NET Core 发展非常迅速&#xff0c;不过短短几年&#xff0c;.NET Core 已经发布 .NET 8 了&#xff0c;基本上保持了一年一个版本的速度&#xff0c;每个版本都有自己的独有特性&#xff0c;下面我们来简单的盘点一下…

从零实现Llama3中文版

1.前言 一个月前&#xff0c;Meta 发布了开源大模型 llama3 系列&#xff0c;在多个关键基准测试中优于业界 SOTA 模型&#xff0c;并在代码生成任务上全面领先。 此后&#xff0c;开发者们便开始了本地部署和实现&#xff0c;比如 llama3 的中文实现、llama3 的纯 NumPy 实现…

【机器学习300问】100、怎么理解卷积神经网络CNN中的池化操作?

一、什么是池化&#xff1f; 卷积神经网络&#xff08;CNN&#xff09;中的池化&#xff08;Pooling&#xff09;操作是一种下采样技术&#xff0c;其目的是减少数据的空间维度&#xff08;宽度和高度&#xff09;&#xff0c;同时保持最重要的特征并降低计算复杂度。池化操作不…

高速数据采集与传输(一):ADC08D500调研

前言&#xff1a;高速ADC数据采集的应用和开发&#xff0c;涉及的技术面非常的广泛&#xff0c;后续阶段博主将尝试以纯项目开发的形式做一次专题技术分享&#xff0c;将基于高速数据采集的相关内容进行一系列的技术文档更新。博主全凭兴趣在更新和总结&#xff0c;很难做到一直…

AI预测体彩排3采取888=3策略+和值012路一缩定乾坤测试5月26日预测第2弹

今天继续基于8883的大底进行测试&#xff0c;昨天的预测已成功命中&#xff01;今天继续测试&#xff0c;按照排三前面的规律&#xff0c;感觉要出对子了&#xff0c;所以本次预测不再杀对子&#xff0c;将采用杀一个和尾来代替。好了&#xff0c;直接上结果吧~ 首先&#xff0…

软考结束。有什么要说的

1. 竟然是机试&#xff0c;出乎我意料。是 考试机构觉得笔试成本高了么。这次的考试是机试&#xff0c;相比以往有所不一样。感言是不是以后都会在固定地点考试也说不准。 2. 遇到年轻人。 这次旁边的一个女同学第一次参加&#xff0c;还像我询问了一些关于软考的事。我是有…

14 vue学习:透传Attributes

Attributes 继承 “透传 attribute”指的是传递给一个组件&#xff0c;却没有被该组件声明为 [props]或 [emits]的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id。 当一个组件以单个元素为根作渲染时&#xff0c;透传的 attribute 会自动被添加到根元…

vcpkg环境配置

vcpkg 使用linux相关库&#xff0c;设置环境变量VCPKG_ROOT&#xff0c;设置cmake工具链$VCPKG_ROOT/scripts\buildsystems\vcpkg.cmake set VCPKG_DEFAULT_TRIPLETx64-windows .\vcpkg.exe install fftw3 freetype gettext glibmm gtkmm libjpeg-turbo libpng libxmlpp libs…

pytest框架用例命名规则详解

pytest 测试用例的命名规则是为了确保 pytest 能够正确地识别和执行测试用例。 以下是关于 pytest 测试用例命名规则的详细解释&#xff1a; 1 单个测试文件以‘test_’开头或者以‘_test’结尾 比如我们创建test_case1.py case2_test.py文件。 2 单个测试文件中&#xff0c…