retentionpolicy的三种级别是source、class、runtime,分别控制注解仅存于源码、保留在字节码但不加载到jvm、或运行时可通过反射读取;选错会导致反射失效、性能下降或内存浪费。

@Retention 不是“存留期”管理工具,它控制的是注解在 Java 生命周期中的可见阶段——源码、字节码还是运行时。所谓“最优存留期”,本质是根据业务是否需要在特定阶段读取注解,来选择最轻量且够用的策略,而非追求“长期保留”。选错会导致反射失效、启动变慢、内存浪费,甚至安全暴露。
什么时候必须用 RUNTIME?
只有当业务逻辑明确依赖运行时反射读取注解时,才需设为 RUNTIME:
- Spring 的
@Autowired、@RequestMapping:容器在启动时扫描并注入,必须反射获取 - JUnit 的
@Test、@BeforeAll:测试框架在运行时遍历方法并执行 - 自定义日志埋点(如
@Trace("pay")):AOP 切面需在方法执行前动态读取注解值
若业务只需在编译后做静态检查或字节码增强(如 Lombok 生成 getter),RUNTIME 就是过度设计。
CLASS 策略适合哪些场景?
RetentionPolicy.CLASS 是默认值,注解写入 class 文件但不进 JVM 内存,无反射开销:
- 构建时工具链处理:Dagger 依赖注入代码生成、ASM 修改字节码
- APK 构建阶段的资源标记(Android 注解处理器)
- 内部 SDK 的元数据标记,仅供构建脚本解析,不暴露给 App 运行时
它比 RUNTIME 更轻,又比 SOURCE 多一层中间态,适合“编译即用、运行即弃”的协作模式。
SOURCE 策略:零成本,纯编译期存在
RetentionPolicy.SOURCE 只存在于 .java 文件中,javac 编译完就消失:
- 编译器提示类注解:
@Override、@SuppressWarnings - 静态分析工具标记:自定义 Lint 规则、FindBugs 注解
- 文档型注解:如
@ApiStatus.Internal,仅作 IDE 提示或生成 Javadoc
它不增加 class 体积、不拖慢类加载、不参与任何运行时流程——只要不需要反射,就该优先考虑 SOURCE。
性能代价必须量化看待
RUNTIME 的成本不是理论上的“轻微影响”,而是可测量的:
- 每个 RUNTIME 注解会使 class 文件增大数十到上百字节(含描述符、常量池项)
- 类加载时,JVM 需解析并缓存注解元数据,百万级类规模下 GC 压力明显上升
- 高频反射调用(如每请求扫描 Controller 方法)会成为 CPU 瓶颈,实测比 CLASS 策略慢 3–5 倍
因此,Spring Boot 2.6+ 已默认禁用部分 RUNTIME 注解的自动扫描,改用 index 方式加速启动——这就是对 RUNTIME 成本的直接回应。










