全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-11 7 分钟 ✍️ juanwangdev

Java编译时注解

编译时注解(APT)在编译期处理注解,生成代码,避免运行时反射开销。

编译时 vs 运行时注解

特性编译时注解运行时注解
处理时机编译期运行期
保留策略SOURCE 或 CLASSRUNTIME
性能无运行时开销反射有性能损耗
典型框架Lombok、ButterKnifeSpring、MyBatis

APT工作流程

Java
源码(.java) → 编译器解析注解 → 调用处理器 → 生成新文件 → 编译新文件 → .class

实战:生成Builder模式代码

1. 定义注解

Java
@Retention(RetentionPolicy.SOURCE)  // 只在源码保留
@Target(ElementType.TYPE)
public @interface GenerateBuilder {
    String prefix() default "with";
}

2. 注解处理器

Java
@SupportedAnnotationTypes("com.example.GenerateBuilder")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class BuilderProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations,
                          RoundEnvironment roundEnv) {
        for (TypeElement annotation : annotations) {
            for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
                if (element.getKind() == ElementKind.CLASS) {
                    generateBuilder((TypeElement) element);
                }
            }
        }
        return true;
    }

    private void generateBuilder(TypeElement classElement) {
        String className = classElement.getSimpleName().toString();
        String packageName = getPackageName(classElement);
        String builderName = className + "Builder";

        // 收集字段
        List<VariableElement> fields = new ArrayList<>();
        for (Element member : classElement.getEnclosedElements()) {
            if (member.getKind() == ElementKind.FIELD) {
                fields.add((VariableElement) member);
            }
        }

        // 生成Builder代码
        try {
            JavaFileObject file = processingEnv.getFiler()
                .createSourceFile(packageName + "." + builderName);

            try (PrintWriter out = new PrintWriter(file.openWriter())) {
                writeBuilder(out, packageName, className, builderName, fields);
            }
        } catch (IOException e) {
            processingEnv.getMessager()
                .printMessage(Diagnostic.Kind.ERROR, e.getMessage());
        }
    }

    private void writeBuilder(PrintWriter out, String packageName,
                             String className, String builderName,
                             List<VariableElement> fields) {
        out.println("package " + packageName + ";");
        out.println();
        out.println("public class " + builderName + " {");

        // 字段
        for (VariableElement field : fields) {
            out.println("    private " + field.asType() + " " + field.getSimpleName() + ";");
        }
        out.println();

        // setter方法
        for (VariableElement field : fields) {
            String name = field.getSimpleName().toString();
            String type = field.asType().toString();
            String methodName = "with" + capitalize(name);

            out.println("    public " + builderName + " " + methodName + "(" + type + " " + name + ") {");
            out.println("        this." + name + " = " + name + ";");
            out.println("        return this;");
            out.println("    }");
            out.println();
        }

        // build方法
        out.println("    public " + className + " build() {");
        out.println("        " + className + " obj = new " + className + "();");
        for (VariableElement field : fields) {
            String name = field.getSimpleName().toString();
            out.println("        obj." + name + " = this." + name + ";");
        }
        out.println("        return obj;");
        out.println("    }");

        out.println("}");
    }

    private String capitalize(String str) {
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }

    private String getPackageName(TypeElement element) {
        Element enclosing = element.getEnclosingElement();
        while (enclosing.getKind() != ElementKind.PACKAGE) {
            enclosing = enclosing.getEnclosingElement();
        }
        return ((PackageElement) enclosing).getQualifiedName().toString();
    }
}

3. 使用生成的Builder

Java
@GenerateBuilder
public class User {
    private String name;
    private int age;
    private String email;
}

// 编译后自动生成UserBuilder,可直接使用
User user = new UserBuilder()
    .withName("张三")
    .withAge(25)
    .withEmail("zhang@example.com")
    .build();

JavaPoet简化代码生成

JavaPoet是Square开源的代码生成库:

text
private void generateBuilder(TypeElement classElement) {
    String className = classElement.getSimpleName().toString();
    String packageName = getPackageName(classElement);

    // 定义Builder类
    TypeSpec.Builder builder = TypeSpec.classBuilder(className + "Builder")
        .addModifiers(Modifier.PUBLIC);

    // 添加字段和方法
    for (Element member : classElement.getEnclosedElements()) {
        if (member.getKind() == ElementKind.FIELD) {
            VariableElement field = (VariableElement) member;

            // 字段
            builder.addField(TypeName.get(field.asType()),
                field.getSimpleName().toString(), Modifier.PRIVATE);

            // setter方法
            MethodSpec setter = MethodSpec.methodBuilder(
                "with" + capitalize(field.getSimpleName().toString()))
                .addModifiers(Modifier.PUBLIC)
                .returns(ClassName.get(packageName, className + "Builder"))
                .addParameter(TypeName.get(field.asType()),
                    field.getSimpleName().toString())
                .addStatement("this.$N = $N", field.getSimpleName(), field.getSimpleName())
                .addStatement("return this")
                .build();
            builder.addMethod(setter);
        }
    }

    // 写入文件
    JavaFile javaFile = JavaFile.builder(packageName, builder.build()).build();
    try {
        javaFile.writeTo(processingEnv.getFiler());
    } catch (IOException e) {
        // 处理异常
    }
}

Lombok原理

Lombok通过修改AST(抽象语法树)实现代码增强:

text
源码 → 解析AST → 注解处理器 → 修改AST → 生成.class

关键区别:标准APT只能生成新文件,Lombok通过hack方式修改原有AST。

编译时注解优势

  1. 性能优势:无运行时反射开销
  2. 类型安全:编译期检查,错误提前发现
  3. IDE支持:生成的代码可被IDE识别
  4. 调试友好:生成的代码可直接查看和调试

注意事项

处理器必须注册到 META-INF/services/javax.annotation.processing.Processor

多轮处理时注意避免重复生成文件

使用 Filer 创建文件,不要用 File API

roundEnv.processingOver() 判断是否最后一轮处理

要点总结

  1. 编译时注解在编译期处理,保留策略为 SOURCE 或 CLASS
  2. 继承 AbstractProcessor 实现 process() 方法
  3. 使用 Filer 生成Java文件,JavaPoet简化代码生成
  4. 相比运行时注解,性能更优、类型更安全
  5. Lombok通过修改AST实现字段注入,比标准APT更强大

📝 发现内容有误?点击此处直接编辑

← 上一篇 Java注解定义
下一篇 → Java运行时注解
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库