0Pricing
Java Academy · 课时

编译时注解处理器

编写 AbstractProcessor,在编译时生成源文件或验证代码

编译时注解处理器 是 CoddyKit 上的免费 Java Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。

什么是注解处理器

注解处理器(APT,即注解处理工具)会在编译期间运行。它们检查源代码中的注解,并可以生成新的源文件、资源文件,或引发编译器错误和警告。

AbstractProcessor:基类

扩展 javax.annotation.processing.AbstractProcessor。重写 process() 以处理带注解的元素。声明支持的注解和 Java 源版本。

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;

@SupportedAnnotationTypes("com.example.GenerateBuilder")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class BuilderProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations,
                           RoundEnvironment roundEnv) {
        for (Element e : roundEnv.getElementsAnnotatedWith(GenerateBuilder.class)) {
            generateBuilder((TypeElement) e);
        }
        return true;
    }
    private void generateBuilder(TypeElement cls) { /* generate source */ }
}

注册处理器

将处理器的完整限定类名注册到 META-INF/services/javax.annotation.processing.Processor 中。构建工具(Maven、Gradle)会自动发现它。

# META-INF/services/javax.annotation.processing.Processor
com.example.BuilderProcessor

生成源文件

使用 processingEnv.getFiler().createSourceFile() 写入新的 .java 文件。随后,编译器会在同一轮或下一轮中编译生成的文件。

JavaFileObject file = processingEnv.getFiler()
    .createSourceFile("com.example.UserBuilder");
try (Writer w = file.openWriter()) {
    w.write("package com.example;\n");
    w.write("public class UserBuilder {\n");
    w.write("  // generated builder\n");
    w.write("}\n");
}

检查元素模型

注解处理器处理的是 Element 元素,而不是 Class 类,因为此时源代码尚未编译。请使用 TypeElement、VariableElement 和 ExecutableElement。

TypeElement classElement = (TypeElement) element;
String className = classElement.getSimpleName().toString();
List<VariableElement> fields = classElement.getEnclosedElements().stream()
    .filter(e -> e.getKind() == ElementKind.FIELD)
    .map(e -> (VariableElement) e)
    .collect(Collectors.toList());

输出编译器错误

调用 processingEnv.getMessager().printMessage() 输出错误、警告或提示信息。自定义约束注解就是通过这种方式在编译时报告违规情况的。

processingEnv.getMessager().printMessage(
    Diagnostic.Kind.ERROR,
    "@GenerateBuilder requires at least one field",
    element
);

多轮处理

注解处理会分轮进行。如果生成的文件包含注解,处理器可能会在下一轮再次运行。roundEnv.processingOver() 表示最后一轮。

if (roundEnv.processingOver()) {
    // last round — write summary files or do cleanup
}

Lombok 的底层实现

Lombok 使用一种非标准的 APT 扩展来修改现有类的 AST(规范不允许这样做)。它的 @Data、@Builder 和 @Getter 等注解会直接将方法生成到已编译的类中。

MapStruct 代码生成

MapStruct 会在编译时生成类型安全的映射器实现。它读取带有 @Mapper 注解的接口,并将实现写成普通的 Java 类,从而在运行时完全不需要反射。

@Mapper
public interface UserMapper {
    UserDto toDto(User user);
    User toEntity(UserDto dto);
}
// MapStruct generates: UserMapperImpl.java at compile time

测试注解处理器

请使用 google/compile-testing 或 Kapt(用于 Kotlin)对处理器进行单元测试。输入源代码字符串,并断言生成的文件或编译器错误。

JavaFileObject src = JavaFileObjects.forSourceString("Test",
    "@GenerateBuilder public class Test { private String name; }");
Compilation c = Compiler.javac().withProcessors(new BuilderProcessor()).compile(src);
CompilationSubject.assertThat(c).succeeded();
CompilationSubject.assertThat(c).generatedSourceFile("TestBuilder");

处理器打包

将处理器打包到单独的 Maven 模块中。注解模块(仅包含注解)是一个 compile 依赖;处理器模块使用 annotationProcessor 作用域,绝不能作为运行时依赖。

// build.gradle:
dependencies {
    implementation "com.example:my-annotations:1.0"
    annotationProcessor "com.example:my-processor:1.0"
}

快速检查

在哪里注册注解处理器,才能实现自动发现?

回顾

扩展 AbstractProcessor,在 META-INF/services 中注册处理器,使用 Filer 生成源代码,并使用 Messager 输出错误。将处理器单独打包。使用 compile-testing 进行单元测试,从而消除运行时的反射开销。

常见问题解答

「编译时注解处理器」课时是免费的吗?

是的 — 「编译时注解处理器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。

「编译时注解处理器」这节课中我会学到什么?

编写 AbstractProcessor,在编译时生成源文件或验证代码 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Java Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「编译时注解处理器」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Java Academy 课中编写并运行代码吗?

能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 定义注解:元素与默认值
  2. 保留策略与目标类型
  3. 运行时注解处理
  4. 编译时注解处理器
← 返回 Java Academy