9.3注解处理器

Java 注解处理器(Annotation Processor)是编译期处理注解的核心工具,它允许开发者在编译时扫描和处理注解,生成额外的代码或资源,从而减少手动编写样板代码的工作量。

一、注解处理器的基本原理

  1. 处理阶段:注解处理器在编译期(javac 编译时)运行,早于代码生成和类加载阶段。
  2. 处理对象:处理器可以读取、解析和处理源代码中的注解,并生成新的源文件、配置文件等。
  3. 隔离性:注解处理器运行在独立的 JVM 进程中,不影响目标程序的运行时行为。

二、核心组件与 API

1. 核心接口
  • Processor:所有注解处理器必须实现的接口。
  • AbstractProcessor:Java 提供的抽象基类,简化处理器开发。
2. 元数据工具
  • Elements:处理程序元素(如类、方法、字段)的工具。
  • Types:处理类型信息的工具。
  • Filer:创建新文件的工具(如生成源文件)。
3. 注解元数据
  • RoundEnvironment:提供当前处理轮次的环境信息,包括被注解的元素。

三、开发步骤

  1. 定义注解:使用@interface定义注解,并设置@Retention(RetentionPolicy.SOURCE)
  2. 实现处理器:继承AbstractProcessor,实现处理逻辑。
  3. 注册处理器:通过META-INF/services/javax.annotation.processing.Processor文件注册。
  4. 编译时触发:在编译时,注解处理器会自动扫描并处理源代码中的注解。

四、综合案例:实现一个简单的 Getter/Setter 生成器

下面通过一个完整的案例,展示如何开发一个自定义注解处理器,自动生成 Java Bean 的 Getter/Setter 方法。

1. 定义注解
// Getter.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {
}

// Setter.java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Setter {
}

// Data.java (组合注解)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Data {
}
2. 实现注解处理器
// AccessorProcessor.java
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;

@SupportedAnnotationTypes({
    "com.example.Getter",
    "com.example.Setter",
    "com.example.Data"
})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AccessorProcessor 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) {
        // 处理@Data注解的类
        for (Element element : roundEnv.getElementsAnnotatedWith(Data.class)) {
            if (element instanceof TypeElement) {
                TypeElement typeElement = (TypeElement) element;
                generateAccessors(typeElement, true, true);
            }
        }

        // 处理@Getter注解的字段
        for (Element element : roundEnv.getElementsAnnotatedWith(Getter.class)) {
            if (element instanceof VariableElement) {
                VariableElement field = (VariableElement) element;
                TypeElement classElement = (TypeElement) field.getEnclosingElement();
                generateAccessors(classElement, true, false);
            }
        }

        // 处理@Setter注解的字段
        for (Element element : roundEnv.getElementsAnnotatedWith(Setter.class)) {
            if (element instanceof VariableElement) {
                VariableElement field = (VariableElement) element;
                TypeElement classElement = (TypeElement) field.getEnclosingElement();
                generateAccessors(classElement, false, true);
            }
        }

        return true;
    }

    private void generateAccessors(TypeElement classElement, boolean generateGetters, boolean generateSetters) {
        String className = classElement.getSimpleName().toString();
        String packageName = processingEnv.getElementUtils().getPackageOf(classElement).toString();

        try {
            JavaFileObject jfo = filer.createSourceFile(packageName + "." + className + "_Accessor");
            try (PrintWriter writer = new PrintWriter(jfo.openWriter())) {
                writer.println("package " + packageName + ";");
                writer.println();
                writer.println("public class " + className + "_Accessor {");
                writer.println();

                // 生成构造方法
                writer.println("    private final " + className + " target;");
                writer.println();
                writer.println("    public " + className + "_Accessor(" + className + " target) {");
                writer.println("        this.target = target;");
                writer.println("    }");
                writer.println();

                // 生成Getter和Setter方法
                for (Element element : classElement.getEnclosedElements()) {
                    if (element instanceof VariableElement) {
                        VariableElement field = (VariableElement) element;
                        String fieldName = field.getSimpleName().toString();
                        String fieldType = field.asType().toString();

                        // 生成Getter
                        if (generateGetters && (
                                field.getAnnotation(Getter.class) != null ||
                                classElement.getAnnotation(Data.class) != null)) {
                            writer.println("    public " + fieldType + " get" + capitalize(fieldName) + "() {");
                            writer.println("        return target." + fieldName + ";");
                            writer.println("    }");
                            writer.println();
                        }

                        // 生成Setter
                        if (generateSetters && (
                                field.getAnnotation(Setter.class) != null ||
                                classElement.getAnnotation(Data.class) != null)) {
                            writer.println("    public void set" + capitalize(fieldName) + "(" + fieldType + " " + fieldName + ") {");
                            writer.println("        target." + fieldName + " = " + fieldName + ";");
                            writer.println("    }");
                            writer.println();
                        }
                    }
                }

                writer.println("}");
            }
        } catch (IOException e) {
            messager.printMessage(Diagnostic.Kind.ERROR, "生成文件失败: " + e.getMessage());
        }
    }

    private String capitalize(String name) {
        if (name == null || name.isEmpty()) {
            return name;
        }
        return Character.toUpperCase(name.charAt(0)) + name.substring(1);
    }
}
3. 注册处理器

src/main/resources/META-INF/services目录下创建javax.annotation.processing.Processor文件,内容为:

com.example.AccessorProcessor
4. 使用注解
// User.java
@Data
public class User {
    private String name;
    private int age;
    private boolean active;
}

// Main.java
public class Main {
    public static void main(String[] args) {
        User user = new User();
        User_Accessor accessor = new User_Accessor(user);
        
        accessor.setName("John");
        accessor.setAge(30);
        
        System.out.println("Name: " + accessor.getName());
        System.out.println("Age: " + accessor.getAge());
    }
}
5. 编译和运行
  1. 编译注解处理器模块

    javac -d processor-output src/com/example/*.java src/META-INF/services/javax.annotation.processing.Processor
    jar cvf processor.jar -C processor-output .
    
  2. 使用处理器编译应用代码

    javac -cp processor.jar -processor com.example.AccessorProcessor src/com/example/*.java
    
  3. 运行程序

    java com.example.Main
    

五、注解处理器的高级特性

1. 增量处理(Java 8+)

通过@SupportedOptionsprocessingEnv.getOptions()支持编译选项。

2. 生成非 Java 文件

使用Filer创建资源文件、配置文件等。

3. 错误处理

通过Messager输出编译期警告和错误:

messager.printMessage(Diagnostic.Kind.ERROR, "字段名不能为null", element);

六、注意事项

  1. 性能考虑:复杂的注解处理器可能影响编译速度。
  2. 处理轮次:处理器可能被多次调用(每个注解类型一次),需处理好状态管理。
  3. 兼容性:不同版本的 Java 编译器可能对注解处理器有不同的支持。

通过注解处理器,开发者可以实现代码生成、编译时检查等功能,极大提高开发效率,这也是 Lombok、Dagger 等框架的核心技术之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chxii

小小打赏,大大鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值