Java 注解处理概览
注解处理器如何在编译期读取注解并生成代码:与运行时反射的分工。
#concept
#growing
#dev
#java
[!info] related notes
Java 注解处理概览
这个概念为什么出现
Dagger、MapStruct 等使用注解处理在 javac 期间生成源码或元数据、检查约束。Lombok 虽接入处理流程,但修改编译器语法树依赖编译器内部机制,不代表标准处理器都能改写现有源码。
[!abstract] 一句话理解 注解处理器在编译轮次中扫描注解并生成代码或报错;它不替代运行时反射,但可减少反射。
最小可观察示例
场景:理解保留策略决定谁能看见注解
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@interface Route { String value(); }
@Route("/health")
public class HealthController {
public static void main(String[] args) {
Route r = HealthController.class.getAnnotation(Route.class);
System.out.println(r.value());
}
}
javac HealthController.java && java HealthController
期望:
/health
说明:这是运行时可见注解演示;真正的处理器实现 Processor SPI,需先编译并由构建显式接入;独立模块是常用组织方式,不是 API 的强制要求。
结合场景再看三个关注点
- SOURCE 不写入 class 文件,CLASS 写入但不可经运行时反射读取,RUNTIME 保留到运行时;未声明
@Retention默认 CLASS。源码编译中的处理器可读取三种保留策略的注解。 - 处理器在编译期执行;可通过
--processor-path或--processor-module-path配置,并用-processor显式指定实现。 - 生成代码应可定位、可清理。
核心概念与准确模型
1. 处理器 API
javax.annotation.processing.Processor / AbstractProcessor
2. 轮次
处理器用 Filer 生成的新源码/类文件可进入后续轮次;不再生成新输入时进入最后一轮(processingOver())。标准 API 支持生成新文件和诊断,不提供任意改写已有类的能力。
3. 与反射分工
| 编译期处理 | 运行时反射 | |
|---|---|---|
| 时机 | javac | 运行 |
| 成本 | 编译变慢 | 运行开销 |
| 典型 | 映射器生成 | 框架扫描 |
边界情况与反直觉行为
- 增量编译与处理器兼容性。
- 生成代码与手写冲突。
- IDE 与命令行处理器路径不一致。
常见误区
[!warning] 常见误区:认为注解本身会“执行” 注解是元数据;行为来自处理器或运行时框架。
工程实践
- 处理器先编译,通常单独模块;显式配置处理器,避免依赖 classpath 自动发现的版本差异。
- 生成目录约定并 git 策略清晰。
- 失败信息指向用户源码位置。
- 版本化处理器与语言级别。
本节总结
- 编译期元编程
- 保留策略重要
- 与反射互补
自测题
- SOURCE 保留的注解运行时能否反射到?
- 注解处理的主要收益?
参考答案
- 不能;SOURCE 注解不保留到 class 文件。编译期处理器生成的额外元数据是另一回事。
- 提前生成/校验,降低运行时反射与配置错误。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Annotation Processing | 文档(历史入口) | 概念 |
| javax.annotation.processing | API | 现代 API |