当前位置: 首页 > news >正文

Java 的 APT(Annotation Processing Tool)机制详解

文章目录

  • 一、认识APT
    • 1、简介
    • 2、工作流程
    • 3、回顾注解
    • 4、Element常用元素
    • 5、Element元素常用变量
  • 二、使用注解处理器
    • 1、项目一:定义注解
    • 2、项目一:实现注解处理器(Processor)
    • 3、项目一:注册处理器
    • 4、项目一:打包
    • 5、项目二:编译期执行
  • 参考资料

一、认识APT

1、简介

APT(Annotation Processing Tool)即注解处理器,它是一种处理注解的工具,也是javac中的一个工具,用于在编译阶段未生成class之前对源码中的注解进行扫描和处理。

APT可以用来在编译时扫描和处理注解, 它可以用来获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。APT获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。

2、工作流程

在这里插入图片描述

3、回顾注解

因为APT = 注解+ 注解处理器(AbstractProcessor)。这里就不详细介绍Java注解了。
特别关注一下下元注解@Retention

@Retention这个注解是用来修饰注解定义的,作用是被修饰的注解可以保存多久,这个注解需要使用参数。
这个参数的类型是RetentionPolicy,所以使用这个注解就要对value赋值。
value的值有且仅有三个:
->RetenionPolicy.CLASS编译器把该注解记录在class文件中。当运行java程序时,JVM不可获取注解信息。这是默认值!
->RetenionPolicy.RUNTIME编译器把该注解记录在class文件中。当运行java程序时,JVM可获取注解信息,程序可以通过反射获取该注解信息
->RetenionPolicy.SOURCE该注解只保存在源代码中,编译器直接丢弃该注解。

因为APT是在java编译器使用,因此@Retention的value通常指定为source或者class,这样可以提高一点性能。就我个人而言,我倾向指定为source

关于注解:https://blog.csdn.net/A_art_xiang/article/details/107985113

4、Element常用元素

element是代表程序的一个元素,这个元素可以是:包、类/接口、属性变量、方法/方法形参、泛型参数。element是java-apt(编译时注解处理器)技术的基础,因此如果要编写此类框架,熟悉element是必须的。
在这里插入图片描述

5、Element元素常用变量

在这里插入图片描述

二、使用注解处理器

1、项目一:定义注解

开发者先定义需要处理的注解(如 @Data、@Inject 等),并指定注解的保留策略为 SOURCE 或 CLASS(APT 只能处理编译期可见的注解,RUNTIME 注解在编译期已被丢弃)。

// 示例:定义一个用于生成Builder模式的注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE)  // 作用于类/接口
@Retention(RetentionPolicy.SOURCE)  // 仅在源码中保留
public @interface GenerateBuilder {
}

2、项目一:实现注解处理器(Processor)

通过继承 javax.annotation.processing.AbstractProcessor 类,重写关键方法来定义注解的处理逻辑。核心方法包括:
init(ProcessingEnvironment env):初始化处理器,获取工具类(如用于生成代码的 Filer、用于输出日志的 Messager 等)。
getSupportedAnnotationTypes():指定当前处理器支持处理的注解类型(全类名)。
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv):处理注解的核心逻辑,通过 roundEnv 获取被注解的元素(类、方法等),并生成新代码。

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;// 声明支持的注解和Java版本、支持的注解
@SupportedAnnotationTypes("com.demo.springbootdemo.test.GenerateBuilder")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class BuilderProcessor extends AbstractProcessor {private Messager messager; // 用于输出日志/错误信息private Filer filer;       // 用于生成Java文件@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);messager = processingEnv.getMessager();filer = processingEnv.getFiler();}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {// 遍历所有被@GenerateBuilder注解的元素for (Element element : roundEnv.getElementsAnnotatedWith(GenerateBuilder.class)) {// 只处理类if (element.getKind().isClass()) {TypeElement typeElement = (TypeElement) element;generateBuilderClass(typeElement);} else {// 非类元素使用注解时报错messager.printMessage(Diagnostic.Kind.ERROR, "@GenerateBuilder can only be used on classes", element);}}return true; // 表示注解已被处理}// 生成Builder类private void generateBuilderClass(TypeElement typeElement) {String className = typeElement.getSimpleName().toString();String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).getQualifiedName().toString();String builderClassName = className + "Builder";try {// 创建Java源文件JavaFileObject file = filer.createSourceFile(packageName + "." + builderClassName);try (Writer writer = file.openWriter()) {// 写入Builder类代码writer.write("package " + packageName + ";\n\n");writer.write("public class " + builderClassName + " {\n");writer.write("    private " + className + " target = new " + className + "();\n\n");// 生成setter方法(简化示例,实际需遍历类的字段)writer.write("    public " + builderClassName + " setId(int id) {\n");writer.write("        target.id = id;\n");writer.write("        return this;\n");writer.write("    }\n\n");writer.write("    public " + builderClassName + " setName(String name) {\n");writer.write("        target.name = name;\n");writer.write("        return this;\n");writer.write("    }\n\n");// 生成build方法writer.write("    public " + className + " build() {\n");writer.write("        return target;\n");writer.write("    }\n");writer.write("}\n");}messager.printMessage(Diagnostic.Kind.NOTE, "Generated Builder class: " + builderClassName);} catch (IOException e) {messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate Builder class: " + e.getMessage());}}
}

3、项目一:注册处理器

APT 需要知道哪些处理器用于处理注解。在 Java 中,使用SPI机制,处理器通过 META-INF/services/javax.annotation.processing.Processor 文件注册,文件内容为处理器的全类名:

org.example.test.BuilderProcessor

4、项目一:打包

将项目一打成jar包,为其他工程服务

5、项目二:编译期执行

当使用 javac 编译代码,或者使用idea进行builder时,APT 会自动扫描注解,调用对应的处理器生成代码。生成的代码会与源文件一起被编译为 class 文件。

import org.example.test.GenerateBuilder;@GenerateBuilder  // 标记需要生成Builder的类
public class User {public int id;public String name;
}

build之后,生成了class文件:
在这里插入图片描述

java代码生成可以考虑借鉴javapoet进行源代码生成

参考资料

https://juejin.cn/post/7220696541307650109

http://www.dtcms.com/a/315128.html

相关文章:

  • 【MyBatis-Plus笔记】MyBatis-Plus详解
  • JuiceFS on Windows: 首个 Beta 版的探索与优化之路
  • 【多智能体cooragent】CoorAgent 系统中 5 个核心系统组件分析
  • 【笔记】ROS1|3 Turtlebot3汉堡Burger建SLAM地图并导航【旧文转载】
  • 数学 理论
  • 基于FAISS和Ollama的法律智能对话系统开发实录-【大模型应用班-第5课 RAG技术与应用学习笔记】
  • Fastapi文件上传那些事?
  • 浅谈 Python 中的 next() 函数 —— 迭代器的驱动引擎
  • MCP进阶:工业协议与AI智能体的融合革命
  • Neat Converter电子书格式转换工具,支持ePub、Azw3、Mobi、Doc、PDF、TXT相互转换,完全免费
  • 龙虎榜——20250804
  • numpy数组拼接 - np.concatenate
  • VPS云服务器Linux性能分析与瓶颈解决方案设计
  • java获取文件编码格式,然后读取此文件,适用于任何格式的文件。
  • 面试题:怎么理解3 次握手与 4 次挥手:TCP 连接的建立与终止
  • 【Unity3D】Shader圆形弧度裁剪
  • 思途Spring学习 0804
  • Unity 实现手机端和电脑项目在局域网内通信
  • 【推荐100个unity插件】Unity 的 Hot Reload 热重载实现,加快unity程序编译速度——FastScriptReload插件
  • MySQL InnoDB 表数据结构存储方式详解
  • pathspec ‘with_def_layout‘ did not match any file(s) known to git`
  • Vue 详情header组件
  • Go语言Context
  • ISO(感光度)的工作原理
  • 接口权限(@SaCheckPermission)
  • ebaz4205矿板以太网连接不稳定问题解决方案
  • SQL基础语法(四个分类、库和表的增删改)
  • 【笔记】ROS1|6 中间人攻击移动过程【旧文转载】
  • 私有化部署即时通讯,企业专属通讯系统BeeWorks
  • 计算机网络:网络号和网络位是不是同一个意思