只有runtime策略的注解才能被反射读取,因其写入.class文件并在类加载时驻留jvm元空间;source编译即丢,class不加载进运行时内存,二者调用getannotation()均返回null。

Java运行时注解能否被读取,关键不在“写了没”,而在@Retention设得对不对。只有RUNTIME策略的注解,才能在程序启动后通过反射拿到;SOURCE和CLASS策略下,getAnnotation()永远返回null——不是代码写错了,是注解根本没进JVM内存。
为什么只有 RUNTIME 能被反射读到
RUNTIME 注解会完整写入 .class 文件,并在类加载阶段被 JVM 加载进元空间(Metaspace),成为类元数据的一部分。此时 Class、Method、Field 等反射对象才能通过底层机制访问它。
而:
- SOURCE 注解:javac 编译完就彻底消失,.class 里找不到任何痕迹
- CLASS 注解:虽保留在 .class 文件中,但 JVM 不加载进运行时内存,反射 API 查不到
RUNTIME 的真实开销不能忽视
它不是“多加一行配置”那么简单,而是带来可量化的运行时负担:
- 每个 RUNTIME 注解会让 .class 文件多出几十至上百字节(含常量池项、属性描述符)
- 类加载时需解析并缓存注解结构,百万级类规模下明显抬高元空间压力和 GC 频率
- 反射调用
getAnnotation()比普通字段访问慢 10–100 倍,Spring 启动期高频扫描实测比 CLASS 慢 3–5 倍 - 可能意外暴露内部逻辑,比如权限规则、埋点开关等设计细节落到生产环境字节码中
什么场景必须用 RUNTIME
只在业务逻辑确需运行时动态决策时启用:
- 框架扫描:Spring 的
@Component、@Controller - 运行时拦截:AOP 切面读取
@LogExecutionTime或@Transactional - 条件控制:如
@Cacheable(enabled = true)、@RequireRole("ADMIN") - 序列化策略:Jackson 的
@JsonFormat(pattern = "yyyy-MM-dd")
别把 RUNTIME 当默认选项
CLASS 是 Java 默认保留策略,不是缺陷,而是设计权衡——它静默存在于字节码,供 ASM、Byte Buddy、APT 工具处理,又不增加运行时成本;SOURCE 更轻,适合 @Override、@SuppressWarnings 这类纯编译期用途。盲目全用 RUNTIME,等于主动引入启动延迟、内存膨胀和潜在泄露风险。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











