0%

编译时处理注解

编译时注解处理:APT 与 Annotation Processor 实战

在 Java 中,编译时注解处理(Compile-Time Annotation Processing)是通过 APT(Annotation Processing Tool) 实现的技术,允许在代码编译阶段扫描和处理注解,生成额外的源文件、配置文件等,而无需在运行时通过反射处理,从而提升性能并减少手动编码。本文将详细解析 APT 的工作原理、注解处理器的实现及实战案例。

APT 核心概念

什么是 APT?

APT 是 Java 提供的编译期注解处理工具,它在 javac 编译阶段 运行,通过注解处理器(Annotation Processor)扫描源代码中的注解,执行自定义逻辑(如代码生成、校验),并输出新的源文件或资源文件。这些生成的文件会与原代码一起被编译为 class 文件。

为什么使用 APT?

  • 性能优势:编译期处理注解,避免运行时反射的性能损耗。
  • 代码自动生成:减少模板代码(如 getter/setter、路由表、序列化逻辑)的手动编写,降低出错率。
  • 编译期校验:提前发现注解使用错误(如参数不合法),避免运行时异常。

注解处理器(Annotation Processor)

注解处理器是 APT 的核心,它是一个实现 javax.annotation.processing.Processor 接口的类(通常继承 AbstractProcessor 简化实现),负责处理特定注解。

核心接口与类

  • Processor 接口:定义注解处理器的基本方法,如 process()(处理注解的核心方法)。
  • AbstractProcessor 抽象类:实现了 Processor 接口,提供默认实现,推荐继承此类。
  • ProcessingEnvironment:提供处理器运行所需的工具类(如获取元素、生成文件)。
  • RoundEnvironment:提供当前编译轮次中被注解标记的元素(类、方法等)。

注解处理器的生命周期

  1. 初始化(init():处理器被创建后调用,获取 ProcessingEnvironment 工具类。
  2. 处理注解(process():编译期多次调用(多轮处理),扫描并处理注解,生成文件。
  3. 清理(close():处理器完成工作后调用,释放资源(可选实现)。

实现自定义注解处理器的步骤

步骤 1:定义待处理的注解

首先创建一个需要在编译期处理的注解(通常声明为 @Retention(SOURCE),仅在源码中保留)。

1
2
3
4
5
// 自定义注解:标记需要生成 Builder 模式的类
@Target(ElementType.TYPE) // 仅用于类
@Retention(RetentionPolicy.SOURCE) // 编译期处理,无需保留到运行时
public @interface GenerateBuilder {
}

步骤 2:实现注解处理器

继承 AbstractProcessor,重写核心方法,实现代码生成逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;

// 声明处理器支持的注解和 Java 版本
@SupportedAnnotationTypes("com.example.GenerateBuilder") // 注解全类名
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 支持的 Java 版本
public class BuilderProcessor extends AbstractProcessor {

private Filer filer; // 用于生成源文件的工具
private Messager messager; // 用于输出编译信息(警告、错误)

// 初始化:获取工具类
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}

// 核心方法:处理注解
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 遍历所有被 @GenerateBuilder 注解的类
for (Element element : roundEnv.getElementsAnnotatedWith(GenerateBuilder.class)) {
// 校验元素类型是否为类
if (element.getKind() != ElementKind.CLASS) {
// 输出错误信息:注解只能用于类
messager.printMessage(Diagnostic.Kind.ERROR,
"@GenerateBuilder 只能用于类", element);
continue;
}

// 转换为类元素,获取类信息
TypeElement classElement = (TypeElement) element;
String className = classElement.getSimpleName().toString(); // 类名
String packageName = getPackageName(classElement); // 包名
String builderClassName = className + "Builder"; // 生成的 Builder 类名

// 生成 Builder 类代码
generateBuilderCode(packageName, className, builderClassName);
}
return true; // 表示注解已处理,无需其他处理器处理
}

// 获取类的包名
private String getPackageName(TypeElement classElement) {
return processingEnv.getElementUtils()
.getPackageOf(classElement)
.getQualifiedName()
.toString();
}

// 生成 Builder 类的源代码
private void generateBuilderCode(String packageName, String className, String builderClassName) {
try (Writer writer = filer.createSourceFile(packageName + "." + builderClassName).openWriter()) {
// 写入包声明
writer.write("package " + packageName + ";\n\n");

// 写入类定义
writer.write("public class " + builderClassName + " {\n");

// 生成 Builder 字段(与原类字段一致)
for (Element enclosedElement : ((TypeElement) processingEnv.getElementUtils()
.getTypeElement(packageName + "." + className)).getEnclosedElements()) {
if (enclosedElement.getKind() == ElementKind.FIELD) {
VariableElement field = (VariableElement) enclosedElement;
String fieldType = field.asType().toString();
String fieldName = field.getSimpleName().toString();
writer.write(" private " + fieldType + " " + fieldName + ";\n");
}
}

// 生成 setter 方法(链式调用)
writer.write("\n");
for (Element enclosedElement : ((TypeElement) processingEnv.getElementUtils()
.getTypeElement(packageName + "." + className)).getEnclosedElements()) {
if (enclosedElement.getKind() == ElementKind.FIELD) {
VariableElement field = (VariableElement) enclosedElement;
String fieldType = field.asType().toString();
String fieldName = field.getSimpleName().toString();
// 链式方法:public Builder setXxx(Xxx xxx) { this.xxx = xxx; return this; }
writer.write(" public " + builderClassName + " set"
+ capitalize(fieldName) + "(" + fieldType + " " + fieldName + ") {\n");
writer.write(" this." + fieldName + " = " + fieldName + ";\n");
writer.write(" return this;\n");
writer.write(" }\n");
}
}

// 生成 build() 方法:创建原类实例并赋值
writer.write("\n public " + className + " build() {\n");
writer.write(" " + className + " obj = new " + className + "();\n");
for (Element enclosedElement : ((TypeElement) processingEnv.getElementUtils()
.getTypeElement(packageName + "." + className)).getEnclosedElements()) {
if (enclosedElement.getKind() == ElementKind.FIELD) {
VariableElement field = (VariableElement) enclosedElement;
String fieldName = field.getSimpleName().toString();
writer.write(" obj." + fieldName + " = this." + fieldName + ";\n");
}
}
writer.write(" return obj;\n");
writer.write(" }\n");

// 工具方法:首字母大写(用于生成 setXxx 方法名)
writer.write("\n private String capitalize(String name) {\n");
writer.write(" if (name == null || name.isEmpty()) return name;\n");
writer.write(" return Character.toUpperCase(name.charAt(0)) + name.substring(1);\n");
writer.write(" }\n");

// 关闭类定义
writer.write("}\n");

} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "生成代码失败:" + e.getMessage());
}
}
}

步骤 3:注册注解处理器

编译器需要知道处理器的存在,需通过 SPI(Service Provider Interface) 机制注册:

  1. 在项目的 src/main/resources 目录下创建以下目录结构:

    1
    2
    3
    META-INF/
    services/
    javax.annotation.processing.Processor
  2. javax.annotation.processing.Processor 文件中写入处理器的全类名:

    1
    com.example.BuilderProcessor

步骤 4:配置构建工具(Maven/Gradle)

Maven 配置

在处理器模块的 pom.xml 中添加依赖和编译插件,确保处理器被正确识别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<dependencies>
<!-- 依赖 JDK 注解 API -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<!-- 禁止在处理器模块内部处理注解(避免循环依赖) -->
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>

在主项目中引入处理器模块作为依赖:

1
2
3
4
5
6
<dependency>
<groupId>com.example</groupId>
<artifactId>builder-processor</artifactId>
<version>1.0.0</version>
<scope>provided</scope> <!-- 仅编译期生效 -->
</dependency>

步骤 5:使用注解并验证

在主项目中创建一个类,添加 @GenerateBuilder 注解:

1
2
3
4
5
6
7
8
// 标记需要生成 Builder 的类
@GenerateBuilder
public class User {
private String name;
private int age;

// 省略 getter/setter(由 Builder 替代)
}

编译主项目后,APT 会自动生成 UserBuilder.java 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.example;

public class UserBuilder {
private String name;
private int age;

public UserBuilder setName(String name) {
this.name = name;
return this;
}

public UserBuilder setAge(int age) {
this.age = age;
return this;
}

public User build() {
User obj = new User();
obj.name = this.name;
obj.age = this.age;
return obj;
}

private String capitalize(String name) {
if (name == null || name.isEmpty()) return name;
return Character.toUpperCase(name.charAt(0)) + name.substring(1);
}
}

使用生成的 Builder 类:

1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
User user = new UserBuilder()
.setName("Alice")
.setAge(20)
.build();
System.out.println(user.getName()); // Alice
}
}

APT 高级技巧与注意事项

  1. 多轮处理(Round Processing)
    处理器可能被调用多次(每轮处理生成的文件会触发新的编译轮次),需通过 RoundEnvironment.processingOver() 判断是否所有处理完成。
  2. 依赖管理
    生成的代码可能依赖其他类,需确保依赖在编译期可见,避免 cannot find symbol 错误。
  3. 错误处理
    使用 Messager 输出错误 / 警告信息(Diagnostic.Kind.ERROR 会终止编译),便于定位问题。
  4. 避免重复生成
    检查文件是否已存在(filer.createSourceFile() 会抛出异常),或通过标记文件避免重复生成。
  5. 使用框架简化开发
    手动编写处理器繁琐,可使用 AutoService(自动生成 SPI 配置)、JavaPoet(优雅生成 Java 代码)等框架:
    • AutoService@AutoService(Processor.class) 自动生成 javax.annotation.processing.Processor 文件。
    • JavaPoet:通过 API 构建类、方法、字段,避免手动拼接字符串(减少语法错误)。

常见应用场景

  • Lombok:通过 @Data@Getter 等注解生成 getter/setter、构造器等代码。
  • Dagger:依赖注入框架,通过注解生成依赖注入代码。
  • ButterKnife:视图绑定框架,通过 @BindView 生成 findViewById 代码。
  • ARouter:路由框架,通过注解生成路由表代码,实现页面跳转

欢迎关注我的其它发布渠道