Java注解处理器开发
约 1773 字大约 6 分钟
javaannotationprocessing
2025-03-18
概述
Java注解处理器(Annotation Processor)是在编译时扫描和处理注解的工具,能够生成新的源代码、资源文件,甚至报告编译错误。Lombok、MapStruct、Dagger、AutoValue等流行框架都基于注解处理器实现。理解注解处理机制,是开发编译时代码生成工具的基础。
注解基础回顾
元注解
// @Retention — 注解的生命周期
@Retention(RetentionPolicy.SOURCE) // 仅源码,编译后丢弃(如@Override)
@Retention(RetentionPolicy.CLASS) // 保留到class文件,运行时不可见(默认)
@Retention(RetentionPolicy.RUNTIME) // 运行时可见(反射可获取)
// @Target — 注解可以标注的位置
@Target(ElementType.TYPE) // 类/接口/枚举/record
@Target(ElementType.FIELD) // 字段
@Target(ElementType.METHOD) // 方法
@Target(ElementType.PARAMETER) // 方法参数
@Target(ElementType.CONSTRUCTOR) // 构造方法
@Target(ElementType.LOCAL_VARIABLE) // 局部变量
@Target(ElementType.ANNOTATION_TYPE) // 注解类型
@Target(ElementType.PACKAGE) // 包
@Target(ElementType.TYPE_PARAMETER) // 类型参数(Java 8+)
@Target(ElementType.TYPE_USE) // 类型使用(Java 8+)
@Target(ElementType.RECORD_COMPONENT) // Record组件(Java 16+)
// @Documented — 注解会出现在JavaDoc中
// @Inherited — 子类继承父类的注解
// @Repeatable — 允许重复标注自定义注解
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface AutoBuilder {
String prefix() default "";
boolean fluent() default true;
}
// 使用
@AutoBuilder(prefix = "with", fluent = true)
public class User {
private String name;
private int age;
}注解处理器架构
多轮处理(Rounds)
注解处理是多轮的。每一轮中,处理器扫描当前的源文件和注解,可能生成新的源文件。新生成的源文件会进入下一轮处理。当某一轮不再生成新文件时,处理结束。
开发注解处理器
1. 定义注解
package com.example.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface GenerateToString {
// 标记注解:标注的类自动生成toString方法
}2. 实现处理器
package com.example.processor;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;
@SupportedAnnotationTypes("com.example.annotation.GenerateToString")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class ToStringProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
Set<? extends Element> annotatedElements =
roundEnv.getElementsAnnotatedWith(annotation);
for (Element element : annotatedElements) {
if (element.getKind() != ElementKind.CLASS) {
processingEnv.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"@GenerateToString can only be applied to classes",
element
);
continue;
}
TypeElement typeElement = (TypeElement) element;
generateToStringHelper(typeElement);
}
}
return true; // true表示注解已被消费,不传给其他处理器
}
private void generateToStringHelper(TypeElement typeElement) {
String packageName = processingEnv.getElementUtils()
.getPackageOf(typeElement).getQualifiedName().toString();
String className = typeElement.getSimpleName().toString();
String helperName = className + "ToString";
try {
JavaFileObject file = processingEnv.getFiler()
.createSourceFile(packageName + "." + helperName);
try (PrintWriter writer = new PrintWriter(file.openWriter())) {
writer.println("package " + packageName + ";");
writer.println();
writer.println("public class " + helperName + " {");
writer.println(" public static String toString(" + className + " obj) {");
writer.println(" StringBuilder sb = new StringBuilder(\"" + className + "{\");");
// 遍历所有字段
boolean first = true;
for (Element enclosed : typeElement.getEnclosedElements()) {
if (enclosed.getKind() == ElementKind.FIELD
&& !enclosed.getModifiers().contains(Modifier.STATIC)) {
String fieldName = enclosed.getSimpleName().toString();
if (!first) writer.println(" sb.append(\", \");");
writer.println(" sb.append(\"" + fieldName + "=\").append(obj." + fieldName + ");");
first = false;
}
}
writer.println(" sb.append(\"}\");");
writer.println(" return sb.toString();");
writer.println(" }");
writer.println("}");
}
} catch (IOException e) {
processingEnv.getMessager().printMessage(
Diagnostic.Kind.ERROR,
"Failed to generate file: " + e.getMessage()
);
}
}
}3. 注册处理器
方式1:META-INF/services文件
// src/main/resources/META-INF/services/javax.annotation.processing.Processor
com.example.processor.ToStringProcessor方式2:AutoService(推荐)
import com.google.auto.service.AutoService;
@AutoService(Processor.class)
@SupportedAnnotationTypes("com.example.annotation.GenerateToString")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class ToStringProcessor extends AbstractProcessor {
// ...
}<!-- Maven依赖 -->
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.1.1</version>
<scope>provided</scope>
</dependency>核心API
ProcessingEnvironment
public abstract class AbstractProcessor implements Processor {
protected ProcessingEnvironment processingEnv;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 可用的工具:
Elements elements = processingEnv.getElementUtils(); // 元素操作
Types types = processingEnv.getTypeUtils(); // 类型操作
Filer filer = processingEnv.getFiler(); // 文件生成
Messager messager = processingEnv.getMessager(); // 消息报告
}
}Element体系
| Element类型 | 对应 | ElementKind |
|---|---|---|
| TypeElement | 类/接口/枚举/Record | CLASS, INTERFACE, ENUM, RECORD |
| VariableElement | 字段/参数/枚举常量 | FIELD, PARAMETER, ENUM_CONSTANT |
| ExecutableElement | 方法/构造方法 | METHOD, CONSTRUCTOR |
| PackageElement | 包 | PACKAGE |
RoundEnvironment
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
// 获取标注了特定注解的元素
Set<? extends Element> elements =
roundEnv.getElementsAnnotatedWith(GenerateToString.class);
// 是否是最后一轮(不再有新的源文件生成)
if (roundEnv.processingOver()) {
// 执行清理或最终验证
}
// 是否有错误发生
if (roundEnv.errorRaised()) {
return false;
}
return true;
}Lombok的实现原理
Lombok与标准注解处理器不同 — 它修改已有的AST(抽象语法树),而非仅仅生成新文件。
Lombok使用了非公开的 com.sun.tools.javac.tree API来操作AST。这不是标准的注解处理方式(标准API不允许修改现有源文件),因此存在与IDE和不同JDK版本的兼容性风险。
// Lombok的核心技巧(简化概念)
// 通过javac内部API获取AST树
JCTree.JCClassDecl classDecl = (JCTree.JCClassDecl) trees.getTree(typeElement);
// 直接修改AST,添加getter方法
classDecl.defs = classDecl.defs.append(generateGetterMethod(...));代码生成最佳实践
使用JavaPoet(推荐)
// JavaPoet提供了流畅的API来生成Java源代码
import com.squareup.javapoet.*;
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, World!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example", helloWorld)
.build();
// 在注解处理器中写入
javaFile.writeTo(processingEnv.getFiler());<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
<version>1.13.0</version>
</dependency>调试注解处理器
# 方式1:Maven编译时远程调试
export MAVEN_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"
mvn compile
# 方式2:Gradle
# gradle.properties
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005
# 方式3:打印诊断信息
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Debug: " + element);常见FAQ
Q: 注解处理器能修改已有的源文件吗?
A: 标准API不能。Filer 只支持创建新文件。Lombok通过非标准的javac内部API实现了AST修改,但这不是推荐做法。标准做法是生成新的辅助类。
Q: 注解处理器在运行时有性能影响吗?
A: 没有。处理器在编译时执行,生成的是标准Java源文件或class文件,运行时不需要处理器参与。@Retention(SOURCE) 的注解不会出现在class文件中。
Q: 注解处理器和反射的区别?
A: 注解处理器在编译时工作(使用 javax.lang.model API),反射在运行时工作(使用 java.lang.reflect API)。编译时处理无运行时开销,但无法获取运行时信息。
Q: 如何测试注解处理器?
A: Google的 compile-testing 库提供了编译时测试支持:
Compilation compilation = Compiler.javac()
.withProcessors(new ToStringProcessor())
.compile(JavaFileObjects.forSourceString("test.User", "..."));
assertThat(compilation).succeededWithoutWarnings();
assertThat(compilation).generatedSourceFile("test.UserToString")
.hasSourceEquivalentTo(...);总结
注解处理器是Java编译时元编程的核心机制。通过继承 AbstractProcessor 并实现 process 方法,可以在编译时扫描注解、生成代码、报告错误。结合 AutoService 自动注册和 JavaPoet 代码生成,可以构建强大的编译时代码生成工具。理解注解处理的多轮机制和Element/TypeMirror API,是开发高质量注解处理器的基础。
贡献者
更新日志
9f6c2-feat: organize wiki content and refresh site setup于