选错@Retention策略会导致注解在目标阶段不可见:SOURCE仅存于源码、编译即丢;CLASS保留在字节码但运行时不可反射获取;RUNTIME是唯一支持反射的策略,但带来体积、加载和GC开销。

选错@Retention策略,注解就“看不见”——不是代码写错了,而是生命周期根本没走到那一步。SOURCE、CLASS、RUNTIME 不是保留时间长短的渐进关系,而是三个互不重叠的硬性阶段:源码、字节码、运行时。跨阶段访问必然失败,且三者之间没有隐式升级路径。
SOURCE:只活在.java文件里,编译完就清零
注解仅存在于源码中,javac 编译时直接丢弃,字节码里完全不存在,连 javap -v 都搜不到任何痕迹。它不参与后续任何环节,零体积、零加载开销、零反射成本。
- 适用场景:纯编译期检查或生成,比如
@Override、@SuppressWarnings、Lombok 的@Getter - 关键判断:如果你不需要在编译后读取、增强或反射,就该用 SOURCE
- 常见误用:想让构建工具(如 Maven 插件)扫描注解却设为 SOURCE——工具根本看不到它
CLASS:进得了.class,进不了JVM内存
注解被写入字节码文件,可用 javap -v 查到 RuntimeInvisibleAnnotations 属性,但 JVM 加载类时不会将其加载进运行时元数据。调用 getAnnotation() 或 isAnnotationPresent() 一律返回 null 或 false。
- 适用场景:需要在编译后、运行前介入的流程,例如 APT(注解处理器)、ASM/Byte Buddy 字节码增强、构建时资源校验
- 关键判断:你依赖的是构建工具或字节码操作库,而不是 Java 反射
- 典型信号:IDE 提示 “Annotation is not retained for reflective access”,就是 CLASS 策略的明确标识
RUNTIME:唯一能被反射捕获的注解生命态
注解完整保留在 JVM 运行时的类元数据中,只有 RUNTIME 才支持 Class.getAnnotation()、Method.isAnnotationPresent() 等反射操作。它是 Spring 的 @Autowired、JUnit 的 @Test、自定义切面注解的底层前提。
立即学习“Java免费学习笔记(深入)”;
- 适用场景:框架动态行为、运行时配置注入、AOP 织入、序列化策略控制
- 硬性要求:只要反射逻辑存在,
@Retention(RetentionPolicy.RUNTIME)就不可省略或替换 - 真实代价:每个 RUNTIME 注解增加 class 文件体积(数十至百字节),类加载时需解析缓存元数据,百万级类规模下 GC 压力上升;高频反射调用实测比 CLASS 慢 3–5 倍
不复杂但容易忽略:默认保留策略是 CLASS,不显式声明 RUNTIME 就无法反射读取。策略选得越重,开销越大;选得越轻,功能越受限。按需取用,才是最优解。


















