> 技术文档 > Java 的 APT(Annotation Processing Tool)机制详解

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、工作流程

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

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是必须的。
Java 的 APT(Annotation Processing Tool)机制详解

5、Element元素常用变量

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

二、使用注解处理器

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 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文件 @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); messager = processingEnv.getMessager(); filer = processingEnv.getFiler(); } @Override public 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 的 APT(Annotation Processing Tool)机制详解

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

参考资料

https://juejin.cn/post/7220696541307650109