Java 的注解处理器(Annotation Processor,基于 JSR 269 标准)是编译期代码生成和分析的强大工具。它允许我们在代码编译阶段读取自定义注解,并根据这些注解动态创建新的 Java 源文件,从而彻底消除大量重复的样板代码(如 Builder、Service Factory 等)。
本教程将展示如何创建并注册一个简单的注解处理器,用于自动生成一个基于特定注解的 Greeter 类。
步骤一:定义自定义注解
我们首先定义一个源级别(SOURCE)的注解,这意味着它只在编译期可见,不会被编译进最终的 .class 文件中。
// package com.example.annotations
import java.lang.annotation.*;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface GenerateGreeter {
String name() default "World";
}
步骤二:实现注解处理器
处理器是核心部分,它继承自 AbstractProcessor。我们需要关注两个关键点:指定支持的注解类型,以及在 process 方法中使用 Filer 来生成新文件。
// package com.example.processor
import com.example.annotations.GenerateGreeter;
import javax.annotation.processing.*;
import javax.lang.model.element.*;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;
@SupportedAnnotationTypes("com.example.annotations.GenerateGreeter")
@SupportedSourceVersion(javax.lang.model.SourceVersion.RELEASE_8)
public class GreeterProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(GenerateGreeter.class)) {
// 确保我们只处理类或接口
if (annotatedElement.getKind() != ElementKind.CLASS && annotatedElement.getKind() != ElementKind.INTERFACE) {
continue;
}
// 获取注解值 (这里我们只使用默认值)
GenerateGreeter annotation = annotatedElement.getAnnotation(GenerateGreeter.class);
String greetingName = annotation.name();
// 确定要生成的类名和包名
String packageName = processingEnv.getElementUtils().getPackageOf(annotatedElement).getQualifiedName().toString();
String generatedClassName = "AutoGreeter_" + annotatedElement.getSimpleName().toString();
// 开始文件生成
try {
generateSourceFile(packageName, generatedClassName, greetingName);
} catch (IOException e) {
// 使用 Messager 报告错误
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "无法生成 Greeter 类: " + e.getMessage());
}
}
// 返回 true 表示我们已经处理了这些注解
return true;
}
private void generateSourceFile(String packageName, String className, String greetingName) throws IOException {
// 使用 Filer 创建一个新的源文件
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(packageName + "." + className);
try (PrintWriter writer = new PrintWriter(sourceFile.openWriter())) {
writer.println("package " + packageName + ";");
writer.println("");
writer.println("public class " + className + " {");
writer.println(" public static String greet() {");
writer.println(" return \"Hello, " + greetingName + "! Generated at compile time.\";");
writer.println(" }");
writer.println("}");
}
}
}
步骤三:注册处理器
Java 编译器需要知道哪些类是注解处理器。这通过标准的 Service Provider Interface (SPI) 机制实现。在你的项目资源目录下(通常是 src/main/resources),创建以下路径和文件:
文件路径: META-INF/services/javax.annotation.processing.Processor
文件内容: 写入你处理器的完整类名。
# META-INF/services/javax.annotation.processing.Processor
com.example.processor.GreeterProcessor
步骤四:使用并测试
现在,我们创建一个被注解的类。假设我们的注解处理器模块已经被编译成 JAR,并在主项目中引用。
// package com.example.app
import com.example.annotations.GenerateGreeter;
@GenerateGreeter(name = "Compiler")
public class MyService {
// 这是一个空类,但它的存在会触发代码生成
}
当我们编译 MyService.java 时,注解处理器会被激活,并在编译输出目录中生成一个新文件 AutoGreeter_MyService.java(包名为 com.example.app):
// 自动生成的代码 (AutoGreeter_MyService.java)
package com.example.app;
public class AutoGreeter_MyService {
public static String greet() {
return "Hello, Compiler! Generated at compile time.";
}
}
现在,我们可以在应用代码中直接调用这个自动生成的类:
// 在主应用中调用
package com.example.app;
public class Main {
public static void main(String[] args) {
// 注意:这个类在编译前并不存在,但编译完成后就能找到它。
System.out.println(AutoGreeter_MyService.greet());
}
}
// 输出: Hello, Compiler! Generated at compile time.
通过 JSR 269,我们成功地将代码生成任务从运行时转移到了编译期,极大地简化了开发流程并保证了类型安全。
汤不热吧