必须用RUNTIME仅当业务需运行时动态反射读取注解值:如Spring的@Autowired、@RequestMapping,JUnit的@Test,自定义AOP切面(@Log、@Retry)及运行时配置驱动行为(@FeatureToggle)。

选对@Retention不是“写完注解顺手加个RUNTIME”,而是根据实际读取时机做最小化保留——能用SOURCE就不用CLASS,能用CLASS就别上RUNTIME。过度保留会直接拖慢启动、增大内存、暴露元数据,且不可逆。
什么时候必须用 RUNTIME?
仅当业务逻辑在运行时**动态依赖反射读取注解值**,且无法提前预知或静态确定时,才需要 RUNTIME:
- Spring 的
@Autowired、@RequestMapping:容器在 Bean 初始化阶段扫描并绑定,必须运行时可见 - 自定义 AOP 切面(如
@Log、@Retry):代理逻辑需在方法执行前实时解析注解参数 - 运行时配置驱动的行为(如
@FeatureToggle("payment-v2")):开关状态随环境变化,不能编译期固化
CLASS 策略适合哪些工程场景?
它保留在 class 文件里,但不进 JVM 堆内存,零反射开销,是“编译后即用、运行即弃”的理想选择:
- 构建时代码生成:Dagger、MapStruct、Android ViewBinding 的注解处理器只在编译期扫描 class 字节码
- APK 或 Jar 包分析:资源标记(
@Keep)、权限声明、SDK 兼容性提示等,仅供构建脚本提取,不参与运行逻辑 - 内部框架的中间态元数据:比如微服务模块间接口契约标记,供 CI 流水线校验,但客户端无需感知
SOURCE 是默认安全起点
只要不需要在编译后任何环节读取注解,就该从 SOURCE 开始。它不产生字节码冗余、不触发类加载解析、不增加 GC 压力:
立即学习“Java免费学习笔记(深入)”;
- 静态检查类注解:
@NonNull、@ApiStatus.Experimental,由 IDE 或 Lint 插件在编辑/编译时消费 - 文档型标记:
@Deprecated、@Hidden,仅用于生成 Javadoc 或控制 API 可见性 - Lombok 类注解(
@Getter、@Builder):javac 插件处理完即丢弃,class 文件里完全不存在
验证与规避风险的关键动作
别靠猜测,用工具确认注解是否按预期存在:
- 用
javap -v YourClass.class | grep -A5 "RuntimeVisibleAnnotations"查 RUNTIME 注解;搜RuntimeInvisibleAnnotations查 CLASS 注解;SOURCE 注解根本不会出现 - 在 Spring Boot 项目中,启用
spring.context.annotation.processing.index=true并生成META-INF/spring.components,可绕过大量 RUNTIME 反射扫描,显著加速启动 - 禁止在 DTO、VO、Entity 等高频创建对象上滥用 RUNTIME 注解(如
@JsonAlias改用@JsonProperty的 CLASS 级替代方案,若 Jackson 版本支持)



















