必须用@retention(runtime)仅当运行时需反射读取注解,如spring依赖注入、web路由绑定、junit测试执行、aop切面拦截;class适用于编译期处理(dagger、字节码增强),source仅用于编译检查(@override、lombok),滥用runtime会显著增加启动耗时、内存占用与gc压力。

选 RUNTIME 不是默认选项,而是明确需要反射读取时的主动选择。滥用它会直接拖慢启动、增大内存占用、抬高 GC 压力——这不是理论风险,而是可测量的开销。
什么情况下必须用 @Retention(RUNTIME)
只有当业务逻辑在 JVM 运行过程中,必须通过反射动态获取注解内容时,才该设为 RUNTIME:
- Spring 容器扫描 Bean:@Component、@Service、@Autowired 等依赖注入行为,发生在应用启动阶段,需反射遍历类和方法
- Web 框架路由绑定:如 @RequestMapping、@GetMapping,请求分发前需读取路径与参数元数据
- 测试框架执行控制:JUnit 的 @Test、@BeforeEach 在运行时被 TestEngine 扫描并调用
- AOP 切面逻辑:比如自定义 @Trace 或 @Retry 注解,需在方法执行前后动态拦截并读取配置值
@Retention(CLASS) 更轻量,也更实用
CLASS 是 javac 的默认策略,注解写入 .class 文件,但不加载进 JVM 内存,无反射成本,适合构建期处理:
- Dagger 或 Hilt 的依赖图生成:编译时解析 @Inject、@Module,生成辅助类,无需运行时存在
- Android 资源标记:如 @LayoutRes、@DrawableRes,由 aapt2 在构建阶段校验并优化资源引用
- 字节码增强工具(ASM、Byte Buddy):给方法打标记用于后续插桩,增强完即丢弃,不暴露给运行时
- 内部 SDK 的构建脚本元数据:比如标记某模块为“灰度可用”,仅供 CI/CD 流水线识别,App 启动时不关心
@Retention(SOURCE) 是零成本底线
SOURCE 注解只活到 javac 编译结束,不进字节码、不占内存、不参与任何运行流程:
- 编译器提示类:@Override、@SuppressWarnings、@Deprecated,纯语法辅助
- 静态分析工具集成:自定义 Lint 规则使用的 @Keep、@RestrictTo,供 IDE 或 gradle lint 任务扫描
- Lombok 处理器基础:@Getter、@Builder 等注解本身是 SOURCE 级,由 lombok-agent 在编译期展开为实际代码
- 文档与协作标记:如 @ApiStatus.Experimental、@Preview,仅影响 Javadoc 生成或 IDE 提示,不参与执行
性能差异不是“轻微”,而是可观测的
不同策略带来的实际开销有明确梯度:
- SOURCE:.class 体积不变,类加载无额外负担,反射调用完全不存在
- CLASS:每个注解增加几字节常量池项,类加载略慢(微秒级),但运行时无反射开销
- RUNTIME:每个注解平均增加数十至百字节 class 体积;百万级类规模下,JVM 需缓存元数据,GC 压力上升;高频反射(如每请求扫 Controller)实测比 CLASS 慢 3–5 倍
Spring Boot 2.6+ 默认禁用全包扫描,改用 spring.components.index 就是对 RUNTIME 成本的直接响应。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











