文章目录
一、认识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进行源代码生成