RUNTIME 是唯一支持反射读取的注解保留策略,因其使注解信息随类元数据加载进JVM元空间,供Class、Method、Field等反射API全程访问;SOURCE仅存于源码,CLASS虽写入字节码但运行时不可见,均无法被反射获取。

因为绝大多数框架级功能依赖反射动态读取注解,而只有 RUNTIME 策略能让注解信息完整存活到运行时并被 Class、Method、Field 等反射对象访问。
RUNTIME 是唯一支持反射读取的策略
Java 的注解生命周期由 @Retention 控制,三种策略中:
- SOURCE:编译完就丢弃,class 文件里根本不存在
- CLASS:写入 class 文件,但 JVM 加载时不加载进元空间,反射无法触达
- RUNTIME:随类元数据一同加载进 JVM 元空间,全程对反射 API 开放
只要用到 method.getAnnotation(YourAnno.class),返回值非 null 的前提就是注解必须声明为 @Retention(RetentionPolicy.RUNTIME)。否则,哪怕语法全对、编译通过,运行时也永远是 null。
主流框架都建立在 RUNTIME 基础上
Spring、MyBatis、JUnit、Jackson 等几乎全部依赖运行时注解驱动:
立即学习“Java免费学习笔记(深入)”;
- @Transactional 事务控制——拦截器在方法执行前读取注解配置
- @RequestMapping 路由映射——启动时扫描 Controller 方法上的注解生成路由表
- @Test 测试识别——JUnit 运行器靠反射发现并执行带该注解的方法
- @NotNull 参数校验——Web 层在请求进入前检查注解约束并抛出异常
- @Cacheable 缓存开关——AOP 在调用前后根据注解属性决定是否走缓存
不加 RUNTIME 的问题很隐蔽
它不会报错,也不会崩溃,只是功能彻底静默失效:
- method.getAnnotation(MyAnno.class) 恒为 null
- 所有基于该注解的增强逻辑(权限拦截、日志埋点、参数校验等)全部跳过
- 开发者容易误以为是反射写法错了,其实注解从类加载那一刻起就“不存在”
什么时候不该用 RUNTIME?
并非所有注解都需要 RUNTIME:
- 编译期检查类(如 @Override、@SuppressWarnings)用 SOURCE 就够了
- Lombok 的 @Getter/@Setter 也是 SOURCE,由编译插件处理,不进运行时
- 字节码增强工具(如 Dagger、某些 APT)可能用 CLASS,避免运行时开销
但只要涉及“运行时动态行为”,比如拦截、路由、校验、注入、序列化,RUNTIME 就不是可选项,而是必要条件。


















