注解能否被读取取决于@retention设置:source仅存于源码供编译器检查,class保留在字节码中供构建工具处理,runtime是唯一支持反射读取的策略,三者不可跨阶段访问。

注解能不能被读到,关键看 @Retention 怎么设——它不决定“有没有写”,而决定“在哪儿还能看见”。选对策略,注解才真正起作用;选错,可能编译没问题,运行时却拿不到信息。
SOURCE:只给编译器看,编译完就丢
这类注解不会出现在 .class 文件里,连字节码里都找不到。它的价值全在编译阶段。
- 适合做静态检查:比如自定义一个 @MustOverride 注解,配合编译插件检查子类是否重写了某个方法
- 用于生成代码:Lombok 的 @Getter、@Setter 默认就是 SOURCE,javac 编译时解析并插入方法,class 文件里不留痕迹
- 不能用于反射,也不能被 ASM 等字节码工具读取——因为根本没写进去
CLASS:进字节码,但不进 JVM 内存
这是 Java 的默认保留策略。注解会写入 .class 文件,但 JVM 加载类时不会把它加载进运行时元数据区。
- 适合构建期处理:比如用 Byte Buddy 或 ASM 在类加载前自动织入日志逻辑,依赖的就是 class 文件里的注解信息
- 不能通过 Class.getAnnotation() 或 Method.getAnnotation() 获取——反射 API 查不到
- 比 RUNTIME 更轻量,不增加运行时内存开销,也不暴露元数据给生产环境
RUNTIME:一直活到程序跑起来
只有设为 RUNTIME,注解才能在运行时通过反射拿到。Spring、Jackson、JUnit 这些框架的注解基本都靠它撑着。
- 必须用在需要动态行为的场景:比如 @Transactional 控制事务边界、@LogExecution 记录方法执行时间、@Valid 触发参数校验
- 注意成本:每个 RUNTIME 注解都会增大 class 文件体积,类加载时多加载一点元数据,还可能泄露设计细节(如内部权限规则)
- 别为了“以后可能要用”就盲目设成 RUNTIME——该用 SOURCE 或 CLASS 的地方用了 RUNTIME,属于隐性技术债
怎么选?只问一个问题:谁来读它?
不是看“功能多高级”,而是看“哪个环节真正需要它”。
- IDE 提示或 javac 插件要检查 → 选 SOURCE
- Gradle/Maven 构建时做字节码改写 → 选 CLASS
- Spring AOP 拦截、自定义注解处理器在运行时解析 → 必须选 RUNTIME
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











