⭐ 推荐:社区规则条款 V1.0

如何用 Java 自定义注解处理器在编译期生成代码?

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-23 15:30 ·10 浏览 ·0 回复
内容摘要

讲解如何用 Java 自定义注解处理器在编译期生成源码:分三模块搭建工程、用 SOURCE 保留策略定义注解、继承 AbstractProcessor 并通过 Filer 写出生成类,跑通完整的编译期生成链路。

读完这篇,你能从零搭出一个在 javac 编译阶段自动生成 Java 源码的自定义注解处理器,并跑通「写注解 → 编译 → 生成类 → 调用生成类」的完整链路。

第一步:搭建三模块工程,明确各自的职责

这一步要拆出三个 Maven 模块:demo-annotation 放注解定义,demo-processor 放处理器实现,demo-app 写业务代码做验证。拆开的原因很重要——**处理器本身是要被编译的,如果注解、处理器、业务代码混在一个模块里,处理器还没编译完就要去处理业务类,顺序必然乱套**。

目录结构:

demo-parent/
├── demo-annotation/src/main/java/com/demo/annotation/GenInfo.java
├── demo-processor/src/main/java/com/demo/processor/GenInfoProcessor.java
├── demo-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
└── demo-app/src/main/java/com/demo/app/User.java

demo-processor 依赖 demo-annotation,demo-app 同时依赖两者(处理器用 provided 作用域即可,它只在编译期需要)。

第二步:定义注解,Retention 必须是 SOURCE

这一步写一个只给编译器看的注解,写完业务类就可以用它打标记。

package com.demo.annotation;

import java.lang.annotation.*;

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

注意:RetentionPolicy.SOURCE 表示编译后丢弃,这正是注解处理器场景的标准做法;写成 RUNTIME 也能跑,但会白白把注解元数据打进 class 文件。另外 @Target 只放开 TYPE,后面处理器里还要对非类元素做兜底报错。

第三步:继承 AbstractProcessor,用 Filer 写文件

这一步实现处理器主体,做完就能在编译时生成 类名 + Info 的新类。核心是重写 process():从 RoundEnvironment 取出所有被 @GenInfo 标注的元素,再用 processingEnv.getFiler().createSourceFile() 拿到文件句柄,用 Writer 直接写源码文本。

package com.demo.processor;

import com.demo.annotation.GenInfo;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.util.Elements;
import javax.tools.*;
import java.io.Writer;
import java.util.Set;

@SupportedAnnotationTypes("com.demo.annotation.GenInfo")
public class GenInfoProcessor extends AbstractProcessor {

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (roundEnv.processingOver()) {
            return false;
        }
        Elements elements = processingEnv.getElementUtils();
        for (Element element : roundEnv.getElementsAnnotatedWith(GenInfo.class)) {
            if (element.getKind() != ElementKind.CLASS) {
                processingEnv.getMessager().printMessage(
                        Diagnostic.Kind.ERROR, "@GenInfo 只能标注在类上", element);
                continue;
            }
            TypeElement type = (TypeElement) element;
            String pkg = elements.getPackageOf(type).getQualifiedName().toString();
            String simpleName = type.getSimpleName().toString();
            String genName = simpleName + "Info";
            try {
                JavaFileObject fo = processingEnv.getFiler()
                        .createSourceFile(pkg + "." + genName, type);
                try (Writer w = fo.openWriter()) {
                    w.write("package " + pkg + ";\n\n");
                    w.write("public class " + genName + " {\n");
                    w.write("    public static String describe() {\n");
                    w.write("        return \"" + simpleName + " 由注解处理器生成\";\n");
                    w.write("    }\n}\n");
                }
            } catch (Exception e) {
                processingEnv.getMessager().printMessage(
                        Diagnostic.Kind.ERROR, "生成失败:" + e.getMessage(), element);
            }
        }
        return true;
    }
}

注意:createSourceFile 的第二个参数一定要把原始元素 type 传进去,否则增量编译时编译器无法建立依赖追踪,改了 User 类不会重新生成。生成失败时不要直接 e.printStackTrace(),用 Messager 的 ERROR 级别输出,错误才会挂到对应元素上并在 IDE 里定位到行号。

第四步:注册处理器,否则处理器不会生效

这一步让 javac 能发现你的处理器,做完编译时才会真正触发。在 demo-processor 的 src/main/resources/META-INF/services/ 下新建文件名必须完全一致的 javax.annotation.processing.Processor,内容只有一行:

com.demo.processor.GenInfoProcessor

然后在 demo-app 的 pom.xml 里配置 maven-compiler-plugin(3.11.0 版本):

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.11.0</version>
  <configuration>
    <annotationProcessorPaths>
      <path>
        <groupId>com.demo</groupId>
        <artifactId>demo-processor</artifactId>
        <version>1.0.0</version>
      </path>
    </annotationProcessorPaths>
  </configuration>
</plugin>

注意:最常见的问题就是忘记这个 META-INF/services 文件——编译会正常成功,但 target/generated-sources 下什么都没有,也不报错。在 IntelliJ IDEA 里还要确认 Settings → Build, Execution, Deployment → Compiler → Annotation Processors 中的 Enable annotation processing 已勾选,并优先用 mvn clean compile 验证,避免 IDE 增量构建掩盖问题。

第五步:写业务类并验证生成结果

这一步确认整条链路跑通。demo-app 里写:

package com.demo.app;

import com.demo.annotation.GenInfo;

@GenInfo
public class User {
    public static void main(String[] args) {
        System.out.println(UserInfo.describe());
    }
}

执行 mvn clean compile,打开 demo-app/target/generated-sources/annotations/com/demo/app/UserInfo.java,就能看到生成的源码;再运行 main 方法,控制台输出 User 由注解处理器生成。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2417.html
转载请注明出处,版权归原作者所有。
他们都看过 1 人浏览过
GJ论坛站长
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~