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的所有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.mfbz.cn/a/646717.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java “错误:编码GBK 的不可映射字符”

环境&#xff1a;JDK-17 本机编码&#xff1a;utf-8 代码编码&#xff1a;GBK 错误&#xff1a;java “错误&#xff1a;编码GBK 的不可映射字符” 解决1&#xff1a;记事本打开java源文件&#xff0c;另存为选择ANSI编码 解决2&#xff1a;复制代码再将编码格式改为utf-8,…

CentOS 7安装alertmanager

说明&#xff1a;本文介绍如何在CentOS 7安装alertmanager&#xff1b; Step1&#xff1a;下载安装包 访问Github仓库&#xff0c;下载对应版本的alertmanager安装包 https://github.com/prometheus/alertmanager/releases 如何查看自己系统的信息&#xff0c;可参考下图中的…

2024.5组队学习——MetaGPT(0.8.1)智能体理论与实战(中):订阅智能体OSS实现

传送门&#xff1a; 《2024.5组队学习——MetaGPT&#xff08;0.8.1&#xff09;智能体理论与实战&#xff08;上&#xff09;&#xff1a;MetaGPT安装、单智能体开发》《2024.5组队学习——MetaGPT&#xff08;0.8.1&#xff09;智能体理论与实战&#xff08;下&#xff09;&…

入门五(项目介绍及登录需求)

软件缺陷判定标准 项目中缺陷的管理流程 使用Excel对于缺陷进行管理 使用工具管理缺陷 一、项目背景 传智作为一个IT教育机构&#xff0c;拥有自己开发且实际运营的产品&#xff1b; 将开发和运营的技术作为授课的内容&#xff0c;对于学员而言学到的都是一手的真实案例和…

Redis数据类型(上篇)

前提&#xff1a;&#xff08;key代表键&#xff09; Redis常用的命令 命令作用keys *查看当前库所有的keyexists key判断某个key是否存在type key查看key是什么类型del key 删除指定的keyunlink key非阻塞删除&#xff0c;仅仅将keys从keyspace元数据中删除&#xff0c;真正的…

【论文复现】LSTM长短记忆网络

LSTM 前言网络架构总线遗忘门记忆门记忆细胞输出门 模型定义单个LSTM神经元的定义LSTM层内结构的定义 模型训练模型评估代码细节LSTM层单元的首尾的处理配置Tensorflow的GPU版本 前言 LSTM作为经典模型&#xff0c;可以用来做语言模型&#xff0c;实现类似于语言模型的功能&am…

空间注意力机制

第一步是沿着通道维度进行最大池化和平均池化&#xff0c;比如下面3*3的特征图&#xff0c;有3个通道。 第二步新特征图进行拼接并经过卷积调整通道数 第三步经过Sigmoid函数后乘到输入上 代码&#xff1a; class SpatialAttention(layers.Layer):def __init__(self):super(S…

弱类型解析

php中 转化为相同类型后比较 先判断数据类型后比较数值 var_dump("asdf"0);#bool(true) var_dump("asdf"1);#bool(false) var_dump("0asdf"0);#bool(true) var_dump("1asdf"1);#bool(true)1、md5撞库 例&#xff1a; <?php incl…

stm32-USART串口外设

配置流程 初始化配置 1.开启时钟&#xff08;打开USART和GPIO的时钟&#xff09; void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState); void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState); void RCC_APB1Periph…

YOLOv10 论文学习

论文链接&#xff1a;https://arxiv.org/pdf/2405.14458 代码链接&#xff1a;https://github.com/THU-MIG/yolov10 解决了什么问题&#xff1f; 实时目标检测是计算机视觉领域的研究焦点&#xff0c;目的是以较低的延迟准确地预测图像中各物体的类别和坐标。它广泛应用于自动…

Ansible02-Ansible Modules模块详解

目录 写在前面4. Ansible Modules 模块4.1 Ansible常用模块4.1.1 Command模块4.1.2 shell模块4.1.3 scrpit模块4.1.4 file模块4.1.5 copy模块4.1.6 lineinfile模块4.1.7 systemd模块4.1.8 yum模块4.1.9 get_url模块4.1.10 yum_repository模块4.1.11 user模块4.1.12 group模块4.…

【每日刷题】Day49

【每日刷题】Day49 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 110. 平衡二叉树 - 力扣&#xff08;LeetCode&#xff09; 2. 501. 二叉搜索树中的众数 - 力扣&…

JavaSE 学习记录

1. Java 内存 2. this VS super this和super是两个关键字&#xff0c;用于引用当前对象和其父类对象 this 关键字&#xff1a; this 关键字用于引用当前对象&#xff0c;即调用该关键字的方法所属的对象。 主要用途包括&#xff1a; 在类的实例方法中&#xff0c;通过 this …

H3CNE-7-TCP和UDP协议

TCP和UDP协议 TCP&#xff1a;可靠传输&#xff0c;面向连接 -------- 速度慢&#xff0c;准确性高 UDP&#xff1a;不可靠传输&#xff0c;非面向连接 -------- 速度快&#xff0c;但准确性差 面向连接&#xff1a;如果某应用层协议的四层使用TCP端口&#xff0c;那么正式的…

DragonKnight CTF2024部分wp

DragonKnight CTF2024部分wp 最终成果 又是被带飞的一天&#xff0c;偷偷拷打一下队里的pwn手&#xff0c;只出了一题 这里是我们队的wp web web就出了两个ez题&#xff0c;确实很easy&#xff0c;只是需要一点脑洞(感觉)&#xff0c; ezsgin dirsearch扫一下就发现有ind…

让大模型变得更聪明三个方向

让大模型变得更聪明三个方向 随着人工智能技术的飞速发展&#xff0c;大模型在多个领域展现出了前所未有的能力&#xff0c;但它们仍然面临着理解力、泛化能力和适应性等方面的挑战。那么&#xff0c;如何让大模型变得更聪明呢&#xff1f; 方向一&#xff1a;算法创新 1.1算…

【ML Olympiad】预测地震破坏——根据建筑物位置和施工情况预测地震对建筑物造成的破坏程度

文章目录 Overview 概述Goal 目标Evaluation 评估标准 Dataset Description 数据集说明Dataset Source 数据集来源Dataset Fields 数据集字段 Data Analysis and Visualization 数据分析与可视化Correlation 相关性Hierarchial Clustering 分层聚类Adversarial Validation 对抗…

linux系统部署Oracle11g:netca成功启动后1521端口未能启动问题

一、问题描述 执行netca命令&#xff0c;进入图形化界面&#xff0c;进行Oracle端口监听设置 #终端输入命令 netca 最终提示设置成功&#xff1a; 但是我们进行下一步“创建数据库”的时候会报错&#xff0c;说数据库端口1521未开启&#xff01; 二、问题处理 使用命令查看开…

【Python特征工程系列】一文教你使用PCA进行特征分析与降维(案例+源码)

这是我的第287篇原创文章。 一、引言 主成分分析&#xff08;Principal Component Analysis, PCA&#xff09;是一种常用的降维技术&#xff0c;它通过线性变换将原始特征转换为一组线性不相关的新特征&#xff0c;称为主成分&#xff0c;以便更好地表达数据的方差。 在特征重要…

【kubernetes】陈述式资源管理的kubectl命令合集

目录 前言 一、K8s 资源管理操作方式 1、声明式资源管理方式 2、陈述式资源管理方式 二、陈述式资源管理方式 1、kubectl 命令基本语法 2、查看基本信息 2.1 查看版本信息 2.2 查看资源对象简写 2.3 配置kubectl命令自动补全 2.4 查看node节点日志 2.5 查看集群信息…