应根据注解读取时机选择:source用于编译期处理(如lombok)、class用于字节码工具(如asm)或构建期aop、runtime仅限运行时反射必需场景(如spring组件扫描),错误选择会导致注解不可见或性能损耗。

注解该用 RetentionPolicy.SOURCE 还是 CLASS?
看它要不要参与编译后处理。比如 @Override 只在编译期校验,用 SOURCE 就够了;但如果你写了个注解要被字节码工具(如 ASM、Byte Buddy)读取,就必须选 CLASS —— JVM 加载时它还在 class 文件里,但运行期反射拿不到。
常见错误:以为“不运行就不用 RUNTIME”,结果用了 CLASS,却在测试里用 Class.getDeclaredAnnotations() 去查,返回空数组——因为 CLASS 注解不会进运行时元数据。
-
SOURCE:仅保留在源码阶段,javac 处理完就丢,IDE 提示、lombok 的@Data(默认)走这条路 -
CLASS:写进 .class 文件,但 JVM 不加载进内存,适合 javap 查看或构建期 AOP -
RUNTIME:加载进 JVM,能被getDeclaredAnnotations()拿到,Spring 的@Component必须选它
为什么 @Retention(RetentionPolicy.RUNTIME) 不能乱加?
每个 RUNTIME 注解都会让 JVM 在类加载时解析并保留其结构,哪怕你从没调用过反射。类越多、注解越复杂(尤其带数组或嵌套注解),启动耗时和内存占用就越明显。
典型场景踩坑:给 DTO 字段加了一堆自定义校验注解(如 @NotBlank),全设成 RUNTIME,结果单元测试跑得慢、Mockito mock 失败(因反射初始化失败)。
立即学习“Java免费学习笔记(深入)”;
- 只对真正需要运行时动态行为的注解开
RUNTIME,比如框架集成点、条件判断依据 - 字段级校验注解如果只配合 Hibernate Validator 用,它内部通过字节码或编译期插件读取,
CLASS足够 - 检查是否真被反射调用:grep 项目代码里有没有
getAnnotation(YourAnno.class)或类似逻辑
RetentionPolicy 和注解处理器(APT)的关系
APT 只能看到 SOURCE 和部分 CLASS 注解——因为它工作在 javac 编译中间阶段,还没生成最终 class。所以如果你写了个注解处理器,却把注解设成 RUNTIME,处理器反而收不到它。
错误现象:自定义注解加了 @Retention(RetentionPolicy.RUNTIME),APT 的 process() 方法里遍历 roundEnv.getElementsAnnotatedWith(YourAnno.class) 总是空。
- APT 必须配
SOURCE(最常用)或显式声明支持CLASS(需重写getSupportedSourceVersion()并确保编译器版本匹配) - 别指望用
RUNTIME让 APT 工作——它根本等不到运行时 - Android 开发中注意:kapt 默认只处理
SOURCE,CLASS需额外配置kapt.includeCompileClasspath = true
Spring Boot 里自定义注解常被忽略的生命周期陷阱
很多人照着 @Controller 写个新注解,直接抄 @Retention(RetentionPolicy.RUNTIME),结果发现 Spring 扫描不到。问题不在生命周期,而在 @Target 和 Spring 的 component-scan 规则。
但生命周期确实会放大问题:比如你写了 @MyService 并设为 CLASS,Spring 的 ClassPathBeanDefinitionScanner 默认只扫描 RUNTIME 级注解,它压根不进扫描逻辑——连“找不到”的日志都不会打。
- Spring 的组件扫描(@ComponentScan)和自动配置(@EnableAutoConfiguration)都依赖
RUNTIME注解 - 若想用
CLASS实现轻量级标记,得自己写BeanDefinitionRegistryPostProcessor,从字节码层面解析 - 测试时容易漏掉:用
@ContextConfiguration加载配置类,但忘了该类上自定义注解没被识别——先确认它是RUNTIME,再查是否被@ComponentScan覆盖到包路径
真正难的不是三选一,而是得同时想清楚:谁在什么时候、以什么方式读这个注解。漏掉任一环节,RetentionPolicy 就成了黑盒开关。










