必须添加@Retention(RetentionPolicy.RUNTIME),否则反射获取注解始终为null;因仅RUNTIME级注解会写入class文件并加载到JVM元空间,供Class/Method/Field等反射对象访问。

要让自定义注解在程序运行时被读取,@Retention(RetentionPolicy.RUNTIME) 是必须加上的元注解。没有它,反射调用 getAnnotation() 永远返回 null,哪怕注解语法完全正确、编译通过。
为什么只有 RUNTIME 支持反射读取
RUNTIME 策略确保注解字面量写入 class 文件,并在类加载进 JVM 后保留在方法区(或元空间)的类元数据中。此时,Class、Method、Field 等反射对象才能通过底层机制访问到注解信息。
- SOURCE 级注解:编译完成即丢弃,class 文件里根本不存在
- CLASS 级注解:存在于 class 文件,但 JVM 不将其加载进运行时内存,反射 API 无法触及
- RUNTIME 级注解:随类元数据一同加载,全程对反射开放
典型运行时注解驱动的业务功能
几乎所有需要“动态行为注入”的框架能力,都依赖 RUNTIME 注解:
- 权限控制:如
@RequireRole("ADMIN"),拦截器在运行时读取并校验用户角色 - 自动埋点:如
@LogExecutionTime,AOP 在方法执行前后计时并记录日志 - 参数校验:如
@NotNull,在 Controller 入参前检查字段是否为空 - 缓存开关:如
@Cacheable(enabled = true),决定是否跳过数据库直查缓存 - 事务管理:Spring 的
@Transactional就是基于 RUNTIME 实现的声明式事务
自定义 RUNTIME 注解的正确写法
定义时需同时满足两个关键条件:
立即学习“Java免费学习笔记(深入)”;
- 显式声明
@Retention(RetentionPolicy.RUNTIME)—— 不能省略,也不能依赖默认的 CLASS - 配合
@Target明确作用位置,例如@Target(ElementType.METHOD)
使用时按标准反射流程操作:
- 通过
clazz.getDeclaredMethod("xxx")获取目标Method对象 - 调用
method.getAnnotation(YourAnno.class)—— 返回非 null 的前提是注解为 RUNTIME 级 - 拿到实例后,再调用其属性方法(如
anno.value())驱动后续逻辑
不加 RUNTIME 会发生的实际问题
现象隐蔽,容易误判为代码写错:
-
method.getAnnotation(MyAnno.class)恒为null - 所有基于该注解的增强逻辑(校验、拦截、路由、注入等)全部失效
- 编译无报错、运行无异常,但功能静默不生效
本质不是反射调用错了,而是注解从 JVM 加载那一刻起就已“不存在”。

















