@retention是java中控制注解生命周期的元注解,决定注解在source(仅源码)、class(默认,存于字节码但运行时不可见)或runtime(可反射读取)阶段保留。

@Retention 是 Java 中控制注解“活多久”的关键元注解。它不决定注解写在哪(那是 @Target 的事),而是明确告诉编译器和 JVM:这个注解信息,该留到哪一阶段——只在写代码时有用?编译完就扔?还是得一路带到运行时供反射调用?选错策略,注解可能“写了等于没写”。
注解的三个存活阶段,对应三种 RetentionPolicy
它接收 RetentionPolicy 枚举值,只有三个选项,各司其职:
-
SOURCE:只存在于 .java 源文件里,javac 编译完立刻丢弃,.class 文件里完全找不到。适合纯编译期用途,比如
@Override、@SuppressWarnings—— 编译器用完即焚。 - CLASS:默认策略。注解会写进 .class 字节码,但 JVM 加载类时不会把它加载进内存,反射 API 查不到。适合字节码处理工具(如 ASM、Javassist)在类加载前做分析,但对运行时逻辑无感。
-
RUNTIME:注解完整保留在 .class 中,并随类一起被 JVM 加载进运行时方法区。这是唯一能让
Class.getAnnotation()、Method.getAnnotation()等反射方法成功取到注解的策略。Spring 的@Component、JUnit 的@Test全靠它撑腰。
为什么 RUNTIME 不是默认值?
保留注解不是免费的,有实际成本:
- 每个 RUNTIME 注解都会增加 .class 文件体积(哪怕几字节);
- JVM 加载类时要解析并驻留更多元数据,拖慢类加载速度,多占一点内存;
- 运行时暴露注解内容,可能泄露设计细节或校验逻辑,带来潜在安全或封装风险。
所以 Java 默认用更轻量的 CLASS,把“要不要活到运行时”的决定权交给开发者——按需开启,不盲目堆砌。
常见踩坑与快速验证方法
新手最容易栽在这两个地方:
- 自定义了注解,也在方法上加了,但反射始终返回 null → 十有八九漏了
@Retention(RetentionPolicy.RUNTIME); - 以为加了 RUNTIME 就能在编译期触发警告或生成代码 → 错。编译期行为靠的是 注解处理器(Annotation Processor),和 @Retention 无关,那是
@SupportedSourceVersion和javax.annotation.processing的地盘。
验证注解是否真在运行时可见?终端执行:
javap -v YourClass.class | grep -A 5 "RuntimeVisibleAnnotations"
看到 RuntimeVisibleAnnotations 行,说明 RUNTIME 生效;若只看到 RuntimeInvisibleAnnotations,那就是 CLASS 策略;SOURCE 注解则根本不会出现在字节码里。
搭配 @Target 才算真正可用
@Retention 控制“存在多久”,@Target 控制“能标在哪”。两者常成对出现,缺一不可:
- 只写 @Retention(RUNTIME),但没加 @Target,编译会报错:“Annotation type not applicable to any element”;
- 正确写法示例:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface LogExecution { String value() default ""; }










