注解处理器与编译时生成(Annotation Processing)!

举报
喵手 发表于 2026/01/15 17:02:32 2026/01/15
【摘要】 开篇语哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,...

开篇语

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

注解处理器核心概念(简要)

  • 运行位置:注解处理器在编译阶段(javac)运行,通过 javax.annotation.processing.Processor 接口(常继承 AbstractProcessor)与 processingEnv 交互。

  • RoundEnvironment / 多轮处理(rounds):编译器会把注解处理划分成若干轮(rounds)。每轮处理器可看到在本轮可处理的元素集合;处理器返回 true 表示“已处理该注解类型”,编译器不会再把同类型注解传给其它处理器。注意:生成新源码可能在下一轮被其他处理器处理。

  • 核心服务(来自 ProcessingEnvironment):

    • Filer:写入新生成的源文件(filer.createSourceFile(...) 或使用 Javapoet 的 javaFile.writeTo(filer))。
    • Messager:报告警告/错误给编译器(messager.printMessage(Diagnostic.Kind.ERROR, ...))。
    • ElementsTypes:帮助解析、比较类型和元素(Element / TypeMirror 等)。

高层流程(实际步骤)

  1. init(processingEnv):缓存 filer/messager/elements/types
  2. process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv):遍历 roundEnv.getElementsAnnotatedWith(YourAnnotation.class)
  3. 对每个元素做:合法性校验(是否为 class、是否 public、是否非抽象、字段是否合法等)。用 messager 报告错误并 continue
  4. 使用 Javapoet 或手工拼字符串生成 .java 源文件并写入 filer
  5. 返回 true(表示已经“消费”该注解)。若生成了新类,编译器会在后续轮次对新类及其注解再次触发处理器(注意避免无限循环生成)。

注册与打包(AutoService)

推荐使用 com.google.auto.service:auto-service 来自动注册处理器,示例:

@AutoService(Processor.class)
public class BuilderProcessor extends AbstractProcessor { ... }

同时需要在 pom.xml/Gradle 中把 auto-service 的 annotationProcessor 加入编译时依赖,并在打包时包含服务声明(auto-service 会自动为你生成 META-INF/services/javax.annotation.processing.Processor)。

实战:实现一个 @GenerateBuilder 的注解处理器(完整示例)

下面给出 可直接拷贝并编译 的实现思路与关键代码。这个处理器为标注的 POJO 生成 XBuilder。实现策略:

  • 收集目标类的字段(非静态、 非常量)。
  • 优先尝试:如果目标类有构造器且参数与字段匹配 → 在 build() 中调用构造器 new Target(a,b,c)
  • 回退:若存在无参构造器 + 对应的 setter 方法 → 在 build()new Target(); instance.setX(this.x); ...
  • 否则报编译错误并提示用户增加无参构造器或 setter,或使用适配器模式。

1) 注解定义

package com.example.builder;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface GenerateBuilder {
}

2) 示例目标类(被注解的 POJO)

package com.example.model;

import com.example.builder.GenerateBuilder;

@GenerateBuilder
public class Person {
    private String name;
    private int age;

    public Person() {} // 如果没有无参构造器,处理器会尝试使用匹配构造器

    public void setName(String name) { this.name = name; }
    public void setAge(int age) { this.age = age; }

    // getters omitted
}

3) Processor(核心:使用 Javapoet 生成 Builder)

主要依赖:com.squareup:javapoetcom.google.auto.service:auto-service

package com.example.processor;

import com.example.builder.GenerateBuilder;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.*;

import javax.annotation.processing.*;
import javax.lang.model.element.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.util.*;

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.example.builder.GenerateBuilder")
public class BuilderProcessor extends AbstractProcessor {

    private Messager messager;
    private Filer filer;
    private Elements elementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.messager = processingEnv.getMessager();
        this.filer = processingEnv.getFiler();
        this.elementUtils = processingEnv.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element el : roundEnv.getElementsAnnotatedWith(GenerateBuilder.class)) {
            if (el.getKind() != ElementKind.CLASS) {
                messager.printMessage(Diagnostic.Kind.ERROR, "@GenerateBuilder can only be used on classes", el);
                continue;
            }
            TypeElement classElement = (TypeElement) el;
            try {
                generateBuilderFor(classElement);
            } catch (Exception e) {
                messager.printMessage(Diagnostic.Kind.ERROR, "Processing failed: " + e.getMessage(), el);
            }
        }
        return true; // we claim the annotation
    }

    private void generateBuilderFor(TypeElement classElement) throws IOException {
        String packageName = elementUtils.getPackageOf(classElement).getQualifiedName().toString();
        String className = classElement.getSimpleName().toString();
        String builderName = className + "Builder";

        // 收集字段(非静态、非常量)
        List<VariableElement> fields = new ArrayList<>();
        for (VariableElement ve : ElementFilter.fieldsIn(classElement.getEnclosedElements())) {
            Set<Modifier> mods = ve.getModifiers();
            if (mods.contains(Modifier.STATIC) || mods.contains(Modifier.FINAL)) continue;
            fields.add(ve);
        }

        TypeSpec.Builder builderClass = TypeSpec.classBuilder(builderName)
                .addModifiers(Modifier.PUBLIC);

        // 为每个字段在 builder 中生成私有字段与 setter 方法
        for (VariableElement field : fields) {
            String fname = field.getSimpleName().toString();
            TypeName ftype = TypeName.get(field.asType());
            FieldSpec fieldSpec = FieldSpec.builder(ftype, fname, Modifier.PRIVATE).build();
            builderClass.addField(fieldSpec);

            MethodSpec setter = MethodSpec.methodBuilder(fname)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(ClassName.get(packageName, builderName))
                    .addParameter(ftype, fname)
                    .addStatement("this.$N = $N", fname, fname)
                    .addStatement("return this")
                    .build();
            builderClass.addMethod(setter);
        }

        // build() 方法:优先尝试匹配构造器,否则尝试无参构造+setter
        MethodSpec buildMethod = createBuildMethod(classElement, fields, packageName, className);
        builderClass.addMethod(buildMethod);

        JavaFile javaFile = JavaFile.builder(packageName, builderClass.build())
                .skipJavaLangImports(true)
                .build();

        javaFile.writeTo(filer);
        messager.printMessage(Diagnostic.Kind.NOTE, "Generated " + builderName + " for " + className);
    }

    private MethodSpec createBuildMethod(TypeElement classElement, List<VariableElement> fields,
                                         String packageName, String className) {

        ClassName target = ClassName.get(packageName, className);
        MethodSpec.Builder mb = MethodSpec.methodBuilder("build")
                .addModifiers(Modifier.PUBLIC)
                .returns(target);

        // 尝试找到匹配字段类型顺序的构造器
        List<ExecutableElement> ctors = ElementFilter.constructorsIn(classElement.getEnclosedElements());
        for (ExecutableElement ctor : ctors) {
            List<? extends VariableElement> params = ctor.getParameters();
            if (params.size() == fields.size()) {
                boolean match = true;
                for (int i = 0; i < params.size(); i++) {
                    if (!processingEnv.getTypeUtils().isSameType(params.get(i).asType(), fields.get(i).asType())) {
                        match = false; break;
                    }
                }
                if (match) {
                    // 生成通过构造器创建实例的代码: return new Target(field1, field2, ...);
                    StringJoiner sj = new StringJoiner(", ");
                    for (VariableElement f : fields) sj.add("this." + f.getSimpleName().toString());
                    mb.addStatement("return new $T($L)", target, sj.toString());
                    return mb.build();
                }
            }
        }

        // 回退:检查是否存在无参构造
        boolean hasNoArgCtor = false;
        for (ExecutableElement ctor : ctors) {
            if (ctor.getParameters().isEmpty()) { hasNoArgCtor = true; break; }
        }
        if (!hasNoArgCtor) {
            // 无法生成
            mb.addStatement("throw new $T($S)", IllegalStateException.class,
                    "No suitable constructor or setters found for " + className);
            return mb.build();
        }

        // 找到无参构造,检查是否存在对应 setter,并生成代码
        mb.addStatement("$T instance = new $T()", target, target);
        for (VariableElement f : fields) {
            String fname = f.getSimpleName().toString();
            String setterName = "set" + fname.substring(0,1).toUpperCase() + fname.substring(1);
            boolean hasSetter = false;
            for (ExecutableElement method : ElementFilter.methodsIn(classElement.getEnclosedElements())) {
                if (method.getSimpleName().toString().equals(setterName) && method.getParameters().size() == 1) {
                    if (processingEnv.getTypeUtils().isSameType(method.getParameters().get(0).asType(), f.asType())) {
                        hasSetter = true; break;
                    }
                }
            }
            if (!hasSetter) {
                // 不能设置这个字段
                mb.addStatement("throw new $T($S)", IllegalStateException.class,
                        "No setter for field " + fname + " in " + className);
                return mb.build();
            } else {
                mb.addStatement("instance.$N(this.$N)", setterName, fname);
            }
        }
        mb.addStatement("return instance");
        return mb.build();
    }
}

说明与限制:

  • 上面示例把构造器参数顺序和字段顺序直接匹配(演示简化策略)。生产中你可能需要更鲁棒的匹配逻辑(按名字匹配参数/字段)。
  • 生成的 Builder 放在目标类相同包下,避免包访问问题。
  • 处理器需要将 javapoetauto-service 放在 annotationProcessor/provided 范畴以避免运行时依赖被打包到最终产物中。

编译与集成(Maven / Gradle 快速提示)

  • Maven:在 maven-compiler-plugin 中声明 annotationProcessorPaths(将处理器 jar、auto-service、javapoet 放进去)或把处理器模块作为 annotationProcessor 依赖。示例片段(概念):
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.8.1</version>
  <configuration>
    <annotationProcessorPaths>
      <path>
        <groupId>com.example</groupId>
        <artifactId>your-processor</artifactId>
        <version>1.0-SNAPSHOT</version>
      </path>
      <!-- auto-service, javapoet if needed -->
    </annotationProcessorPaths>
  </configuration>
</plugin>
  • Gradle (Kotlin/Java DSL):把处理器放到 annotationProcessor 配置,源集会自动包含生成目录(build/generated/sources/annotationProcessor/...)。
  • 推荐把注解、处理器、使用示例拆成3 个模块api/annotationsprocessorapp),便于开发与发布。

编译期校验与错误报告

  • 使用 messager.printMessage(Diagnostic.Kind.ERROR, "...", element) 报错会让编译失败并把错误关联到源代码行;务必给出可操作的错误提示(例如“没有可访问的无参构造或 setter”)。
  • 常见校验:注解目标类型检查(class/interface/enum)、禁止注解 abstract class、检查字段类型是否可序列化(如果你生成序列化代码)、检查依赖注解是否存在冲突。

常见陷阱与调试技巧

  • 无限生成循环:生成的类上又带注解,会被再次处理——要设计处理器只处理原始 source 或用 return true 明确声明已处理,或在生成类时使用不同注解/标识避免被重复处理。
  • 元素遍历失误:不要用 classElement.getEnclosedElements() 直接假设包含父类字段;若需包含继承字段,使用 elementUtils.getAllMembers(classElement) 并过滤 FIELD
  • 访问修饰符问题:生成代码位于同包可以访问 package-private 成员;若目标字段为 private 且没有 setter,则无法直接赋值。
  • 类型比较:比较 TypeMirror 时用 processingEnv.getTypeUtils().isSameType(...),不要用 equals
  • Javac vs IDE 行为:IDE(如 Eclipse/IntelliJ)各自的编译器可能与 javac 行为有微妙差异;在 CI 上用 javac 测试更稳妥。
  • 依赖注入/模块化注意:如果项目使用 JPMS(module-info),处理器需要正确导出服务并在 module path 上配置,注册方式更复杂。

测试你的处理器

  • 单元测试工具:推荐 Google 的 compile-testing(可以在单元测试中模拟源码并断言生成结果),或写一个小 demo 模块把处理器当作 annotationProcessor 加入,运行 mvn -T
  • 在 CI(例如 GitHub Actions)上把编译步骤包含进来,确保生成器在 clean 环境下能被执行。

进阶/扩展方向

  • 生成更复杂的 Builder:支持必填字段(在 build() 中检查 null)、方法链校验、泛型类型的 Builder(需处理 TypeMirrorDeclaredType),或支持 Lombok 风格的注解参数(例如 @Builder(toBuilder=true))。
  • 生成工厂(Factory)或注册表:在处理多类注解时向统一的 Meta 文件写入信息(使用 Filer.createResource 写入 JSON 或 service 文件),以便运行时反射加载或进行自动注册。
  • 与 runtime reflection 的比较:编译器生成代码在运行时不依赖反射更安全、性能好、出错早(编译期),但灵活性不如运行时生成;常见做法是把复杂逻辑在编译期生成“骨架”,运行时只负责少量反射或注入。

一页快速检查清单(Checklist)

  • [ ] 注解是否只标注在预期元素上(class/field/method)
  • [ ] 处理器 init 中缓存 filer/messager/elements/types
  • [ ] 对每个注解元素做严格校验并通过 messager 报错反馈
  • [ ] 使用 Javapoet 或 filer 安全写出源文件(避免重复写)
  • [ ] 考虑多轮(round)影响并避免无限循环生成
  • [ ] 在构建脚本中正确配置 annotationProcessor 路径
  • [ ] 写单元测试(compile-testing)或示例模块验证生成源代码

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。


版权声明:本文由作者原创,转载请注明出处,谢谢支持!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。