选对@Retention本质是将注解精准交付给真正需要它的环节:需反射读取时必须用RUNTIME,编译期检查优先SOURCE,字节码处理适用CLASS;选高导致冗余开销,选低则功能失效。

选对 @Retention,本质是把注解交给真正需要它的那个环节——不是越晚丢弃越好,也不是越早清理越安全,关键看“谁在什么时候读它”。
需要反射读取?必须用 RUNTIME
只要你的逻辑依赖 getAnnotation()、isAnnotationPresent() 这类反射调用,就必须声明 @Retention(RetentionPolicy.RUNTIME)。否则运行时永远拿不到注解,返回 null,功能静默失效。
- Spring 的
@Component、@Transactional:启动时扫描注册 Bean 或开启事务 - 权限控制注解如
@RequiresRole("ADMIN"):请求进来时动态校验角色 - 日志埋点
@LogExecutionTime:AOP 在方法执行前后计时 - 参数校验
@NotNull:Controller 入参前触发校验逻辑
只在编译时起作用?优先选 SOURCE
SOURCE 级注解连字节码都不进,纯粹服务编译器或 IDE,零运行时开销,也杜绝了被误读的风险。
-
@Override、@SuppressWarnings:编译器校验后立即丢弃 - 团队内部提示注解,如
@Todo("v2.0重构"):仅需 IDE 高亮,不进生产包 - 配合 ErrorProne 或 Checkstyle 写静态检查规则:在编译阶段报错或警告
要处理字节码但不走反射?用 CLASS
CLASS 是默认策略,注解写进 .class 文件,但 JVM 加载时不进内存,反射读不到——这恰恰适合构建期工具链。
立即学习“Java免费学习笔记(深入)”;
- Lombok 的
@Data、@Builder:编译期生成代码,运行时完全无感知 - Dagger/Hilt 的
@Inject:APT 扫描生成辅助类,运行时不反射 - 自定义 ASM 字节码分析:比如检查某方法是否加了
@InternalApi
别踩这些常见坑
没写 @Retention 就用默认的 CLASS,看似稳妥,但容易埋雷:你以为它只用于编译,结果某天想反射读取,发现读不到。
-
RUNTIME注解会增加 class 文件体积、类加载时间、GC 压力 - 高频反射(如每请求遍历方法注解)建议缓存注解实例,避免性能瓶颈
- 该用
SOURCE却用了RUNTIME,不会报错,但暴露了不该存在的元数据 - 该用
RUNTIME却用了CLASS,反射直接失效,功能白写


















