Java 注解处理器(APT)实战:自定义编译时注解生成代码

1. 注解处理器核心概念
  • APT(Annotation Processing Tool):Java 编译时处理注解的工具,在源码编译阶段扫描并处理注解。
  • 核心优势:零运行时开销,生成的代码直接参与编译。
  • 典型应用:Lombok、ButterKnife、Dagger 等框架。
2. 实战步骤
2.1 创建自定义注解
// 定义编译时注解
@Retention(RetentionPolicy.SOURCE) // 仅保留在源码阶段
@Target(ElementType.TYPE) // 作用于类
public @interface AutoBuilder {
}

2.2 实现注解处理器
@AutoService(Processor.class) // Google AutoService 自动注册
@SupportedAnnotationTypes("com.example.AutoBuilder") // 处理的注解
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class BuilderProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, 
                           RoundEnvironment roundEnv) {
        
        // 遍历所有被 @AutoBuilder 标记的类
        for (Element element : roundEnv.getElementsAnnotatedWith(AutoBuilder.class)) {
            if (element.getKind() == ElementKind.CLASS) {
                TypeElement classElement = (TypeElement) element;
                // 生成 Builder 类
                generateBuilderClass(classElement);
            }
        }
        return true;
    }

    private void generateBuilderClass(TypeElement classElement) {
        String className = classElement.getSimpleName() + "Builder";
        String packageName = processingEnv.getElementUtils()
                .getPackageOf(classElement).toString();

        try {
            // 创建新的 Java 源文件
            JavaFileObject builderFile = processingEnv.getFiler()
                    .createSourceFile(packageName + "." + className);

            try (PrintWriter writer = new PrintWriter(builderFile.openWriter())) {
                // 生成 Builder 类代码
                writer.println("package " + packageName + ";");
                writer.println("public class " + className + " {");
                writer.println("    private final " + classElement.getSimpleName() + " target;");
                writer.println("    public " + className + "() { this.target = new " + classElement.getSimpleName() + "(); }");
                
                // 为每个字段生成 setter 方法
                for (Element field : classElement.getEnclosedElements()) {
                    if (field.getKind() == ElementKind.FIELD) {
                        String fieldName = field.getSimpleName().toString();
                        String fieldType = field.asType().toString();
                        writer.println("    public " + className + " " + fieldName + "(" + fieldType + " value) {");
                        writer.println("        target." + fieldName + " = value;");
                        writer.println("        return this;");
                        writer.println("    }");
                    }
                }
                
                writer.println("    public " + classElement.getSimpleName() + " build() { return target; }");
                writer.println("}");
            }
        } catch (IOException e) {
            throw new RuntimeException("生成 Builder 失败: " + e);
        }
    }
}

2.3 配置依赖 (Maven)
<dependencies>
    <!-- 注解处理器依赖 -->
    <dependency>
        <groupId>com.google.auto.service</groupId>
        <artifactId>auto-service</artifactId>
        <version>1.0.1</version>
    </dependency>
    <dependency>
        <groupId>com.squareup</groupId>
        <artifactId>javapoet</artifactId> <!-- 可选:更优雅的代码生成 -->
        <version>1.13.0</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- 编译时启用注解处理 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>com.google.auto.service</groupId>
                        <artifactId>auto-service</artifactId>
                        <version>1.0.1</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

3. 使用示例
// 应用自定义注解
@AutoBuilder
public class User {
    String name;
    int age;
}

// 编译后自动生成 UserBuilder 类
User user = new UserBuilder()
    .name("Alice")
    .age(30)
    .build();

4. 关键机制解析
组件 作用 重要方法
AbstractProcessor 处理器基类 process()
ProcessingEnvironment 处理环境 getFiler() (文件操作)
RoundEnvironment 扫描上下文 getElementsAnnotatedWith()
Filer 代码生成器 createSourceFile()
5. 调试技巧
  1. 启用编译日志
    mvn clean compile -X
    

  2. 查看生成代码
    target/generated-sources/annotations 目录下检查生成的 .java 文件
  3. 错误处理
    使用 processingEnv.getMessager().printMessage() 输出诊断信息
6. 高级场景
  • 代码优化:使用 JavaPoet 替代手动拼接代码
  • 多轮处理:通过 RoundEnvironment.processingOver() 判断处理轮次
  • 元注解处理:结合 @SupportedAnnotationTypes("*") 实现全局扫描

注意事项

  • 注解处理器不能修改已有代码,只能生成新文件
  • 处理器需独立模块或指定 annotationProcessorPath
  • 避免循环依赖(如处理器不能依赖自身生成的代码)

通过此实战教程,您可快速掌握 APT 的核心开发流程,实现高效的编译时代码生成,大幅提升项目可维护性。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐