Java注解结合APT可在编译期生成源码,提升性能与类型安全;核心步骤为定义SOURCE保留级注解、编写继承AbstractProcessor的处理器、注册并触发编译生成。

Java 注解结合 APT(Annotation Processing Tool)可以在编译期自动生成 Java 源码,无需运行时反射,提升性能与类型安全。核心在于:定义注解 → 编写注解处理器 → 注册处理器 → 编译时触发生成。
定义可被处理的注解
注解必须声明 @Retention(RetentionPolicy.SOURCE)(仅保留到源码阶段)和 @Target(如 ElementType.TYPE 或 ElementType.FIELD),确保它不会进入字节码,只供 APT 使用。
例如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface GenerateService {
String value() default "";
}
编写 AnnotationProcessor 实现类
继承 AbstractProcessor,重写 process() 方法,在其中解析被注解的元素,并用 Filer 生成新 Java 文件。
立即学习“Java免费学习笔记(深入)”;
- 通过
RoundEnvironment获取所有被该注解标记的Element(如类、字段) - 使用
Filer创建新的.java源文件(注意包名与路径需严格匹配) - 借助
JavaFileObject的Writer写入内容,或更推荐使用JavaPoet等库构造结构化代码
示例片段(简化):
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(GenerateService.class)) {
if (element.getKind() == ElementKind.CLASS) {
String className = element.getSimpleName().toString();
String packageName = elements.getPackageOf(element).getQualifiedName().toString();
try {
JavaFileObject file = filer.createSourceFile(packageName + ".Generated" + className);
try (Writer writer = file.openWriter()) {
writer.write("package " + packageName + ";\npublic class Generated" + className + " { }");
}
} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate: " + e);
}
}
}
return true;
}
注册处理器并参与编译
APT 处理器需在 META-INF/services/javax.annotation.processing.Processor 中声明全限定类名,否则 javac 不会加载它。
- Maven 项目中,将处理器模块打包为 jar,并确保其
resources/META-INF/services/...路径存在正确配置 - 主项目依赖该处理器 jar 时,需用
provided或compileOnly范围(Gradle),避免运行时引入 - 启用注解处理:Maven 默认开启;Gradle 需确认
annotationProcessor配置已添加
配合 JavaPoet 提升生成质量
手写字符串拼接易出错、难维护。JavaPoet 是 Google 推出的代码生成库,支持类型安全地构建类、方法、注解等。
- 用
TypeSpec构建类,MethodSpec构建方法,自动处理缩进、分号、导入语句 - 通过
ClassName.get()引用已有类型,避免硬编码类名,增强重构友好性 - 生成结果直接交由
Filer输出,保持与 APT 生命周期一致
例如生成一个带构造函数的类:
TypeSpec serviceClass = TypeSpec.classBuilder("Generated" + className)
.addModifiers(Modifier.PUBLIC)
.addMethod(MethodSpec.constructorBuilder()
.addParameter(String.class, "name")
.addStatement("this.name = name")
.build())
.addField(String.class, "name", Modifier.PRIVATE)
.build();
JavaFile javaFile = JavaFile.builder(packageName, serviceClass).build();
javaFile.writeTo(filer);


















