APT是javac在编译早期调用的注解处理器,基于JSR 269规范和Processor接口,扫描源码、读取SOURCE/CLASS级注解并生成新Java文件,不修改原类,全过程在class生成前完成。

Java 中的编译期注解处理器(APT)不是靠运行时反射,而是由 javac 在编译早期阶段主动调用注册好的处理器,扫描源码、读取注解元数据,并生成新 Java 文件——整个过程发生在 class 字节码生成之前。
APT 的核心支撑:JSR 269 和 Processor 接口
从 Java 6 开始,官方通过 JSR 269 规范定义了可插拔的注解处理 API。所有 APT 行为都围绕 javax.annotation.processing.Processor 接口展开。实际开发中,通常继承 AbstractProcessor,它已封装了初始化、类型匹配、错误报告等基础逻辑。
关键点:
- 处理器必须声明支持哪些注解类型(通过
getSupportedAnnotationTypes()或@SupportedAnnotationTypes) - 必须声明支持的 Java 源码版本(如
@SupportedSourceVersion(RELEASE_17)) - 核心逻辑写在
process()方法里,每次编译轮次都会传入 RoundEnvironment,从中获取被注解标记的元素(类、字段、方法等) - 通过 ProcessingEnvironment 提供的
Filer生成 .java 文件,用Elements解析结构,用Types做类型判断
注解本身必须是编译期可见的
注解能否被 APT 捕获,取决于它的保留策略:@Retention(RetentionPolicy.SOURCE) 或 @Retention(RetentionPolicy.CLASS) 才行。
立即学习“Java免费学习笔记(深入)”;
原因很直接:
-
SOURCE:只保留在源码中,javac 能看到,运行时完全不可见 —— 最适合纯代码生成场景(如 @Data、@Builder) -
CLASS:保留在 class 文件里但 JVM 不加载 —— 适合需要编译后工具(如字节码增强)配合的场景 -
RUNTIME:虽然也能被 APT 处理,但属于“过度保留”,增加 class 体积,且违背编译期处理的设计初衷
处理器如何被 javac 发现并启用
javac 不会自动扫描项目里的 Processor 类。它依赖 Java 的 SPI(Service Provider Interface)机制:
- 在处理器模块的
META-INF/services/目录下,创建文件 javax.annotation.processing.Processor - 文件内容写上实现类的全限定名,例如:
com.example.MyAnnotationProcessor - 编译时把该 jar(或 classpath)传给 javac,它就会按约定加载并实例化处理器
- 现代构建工具(Maven/Gradle)通过
annotationProcessor依赖配置自动完成这一步,无需手动维护 META-INF
生成代码而非修改原类:安全且可控
标准 APT 不能修改已有类的语法树(那是 Lombok 等库借助 javac 内部 API 实现的扩展能力),但它能可靠地生成全新 Java 文件:
- 比如 @Loggable 注解触发生成
MyClass$$Logger.java - 生成的文件和原始源码一起参与后续编译,最终合并进 class 输出
- 这种“只增不改”的设计避免了编译器兼容性风险,也便于调试(生成的代码可直接查看)


















