必须用RUNTIME:当注解需在运行时被反射动态读取处理,如Spring依赖注入、JUnit测试执行、AOP权限校验等;CLASS适用于编译后字节码工具处理;SOURCE仅限编译期检查。

Java注解的生命周期由@Retention元注解控制,它决定注解信息“活到哪一关”——是只在写代码时起作用,还是能进字节码,甚至撑到程序跑起来。选错策略,注解就可能“看不见、读不到、用不了”。关键不在功能多强,而在阶段匹配。
什么时候必须用 RUNTIME?
当你的注解需要在程序运行中被动态识别和处理时,RUNTIME 是唯一选择。框架级能力基本都依赖它。
- Spring 的
@Autowired、@Component靠反射扫描并注入 Bean - JUnit 的
@Test、@BeforeAll由测试引擎在运行时查找并执行 - 自定义序列化逻辑(如根据
@JsonIgnore跳过字段)需在运行时读取注解值 - 权限校验、日志埋点、接口参数校验等 AOP 场景,都得靠反射实时获取注解元数据
CLASS 策略适合哪些实际场景?
CLASS 表示注解写进了 .class 文件,但 JVM 加载类时不加载它——这对人不可见,却对构建期工具很友好。
- APT(Annotation Processing Tool)在编译后生成辅助代码,比如 ButterKnife 旧版或早期 Dagger
- 字节码增强工具(ASM、Javassist)在类加载前分析结构并插入逻辑,例如某些监控 SDK
- 构建流水线中的静态检查插件,比如自定义 Lint 规则或合规性扫描器
- 不希望暴露内部设计给最终用户,又需要在打包阶段做自动化处理的场景
SOURCE 策略不是“没用”,而是“够用就好”
SOURCE 注解连字节码都不进,纯粹服务编译器。它轻量、零运行时开销,是编译期语义检查的理想载体。
立即学习“Java免费学习笔记(深入)”;
-
@Override和@SuppressWarnings就是典型 SOURCE 注解:编译器用完即弃,不占任何运行资源 - 团队内部约定的开发规范注解,比如
@DeprecatedInNextVersion,仅提醒开发者,无需上线 - 配合 IDE 或编译插件做语法提示、自动补全或错误拦截,比如标记实验性 API
- 避免把调试用注解(如
@DebugOnly)意外带入生产环境
默认值是 CLASS,但别惯着它
如果你没写 @Retention,Java 默认按 CLASS 处理。这看似稳妥,实则容易埋坑。
- 想用反射读取却忘了加
RUNTIME?运行时getAnnotation()返回 null,问题难定位 - 本只需编译检查的注解设成 RUNTIME?徒增 class 文件体积、类加载时间与内存占用
- 安全敏感的配置(如加密策略标记)若误设为 RUNTIME,可能通过反射被恶意探测
- 策略应严格遵循“最小可见原则”:只在真正需要的阶段保留,不多留一秒


















