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

发布于:2025-08-05 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、认识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文件

    @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代码生成可以考虑借鉴javapoet进行源代码生成

参考资料

https://juejin.cn/post/7220696541307650109


网站公告

今日签到

点亮在社区的每一天
去签到