Java注解本身不执行逻辑,其生命周期由@Retention指定的RetentionPolicy决定:SOURCE仅存于源码、零运行时开销;CLASS保留在字节码供构建工具使用;RUNTIME保留至运行时支持反射,但带来性能损耗,应按需谨慎选用。

Java 注解本身不参与运行逻辑,JVM 并不直接“执行”注解,而是通过字节码中保留的元数据(attribute)让运行时或编译期工具能读取它。关键在于:注解是否保留在字节码中、以何种形式存在、以及 JVM 如何暴露这些信息。
注解必须声明 RetentionPolicy
只有被 @Retention 指定为 RetentionPolicy.CLASS 或 RetentionPolicy.RUNTIME 的注解,才会写入 class 文件。默认是 CLASS,即编译后保留在字节码中但不加载进 JVM 运行时内存;RUNTIME 才支持反射读取。
-
SOURCE:仅在源码阶段有效,编译后丢弃,不会出现在字节码里 -
CLASS:写入 class 文件的RuntimeVisibleAnnotations或RuntimeInvisibleAnnotations属性,但 ClassLoader 不解析成Annotation对象 -
RUNTIME:同样写入上述属性,且 JVM 在加载类时会解析并支持Class#getDeclaredAnnotations()等反射调用
字节码中用 Annotation 属性存储结构化数据
编译器将注解序列化为 class 文件中的 RuntimeVisibleAnnotations(对应 @Retention(RUNTIME))或 RuntimeInvisibleAnnotations(对应 @Retention(CLASS))属性。每个属性是一个注解数组,每个注解包含:
- 注解类型全限定名(如
Ljava/lang/Override;) - 成员名与值对(name-value pairs),值可以是基本类型、String、Class、枚举、其他注解、或以上类型的数组
- 所有值都被编码为常量池索引(CONSTANT_Utf8、CONSTANT_Integer 等)和结构化描述符
例如 @Deprecated 是一个无参标记注解,字节码中就只存类型描述符;而 @RequestMapping(value = "/api", method = GET) 会分别记录 "value" 和 "method" 的符号引用及实际值。
JVM 通过 ClassFile 解析机制暴露注解
JVM 规范要求虚拟机在加载类时解析 class 文件结构。当遇到 RuntimeVisibleAnnotations 属性,且类被反射访问注解时(如调用 getAnnotations()),HotSpot 等主流 JVM 会:
- 从属性中还原注解类型(查常量池,加载对应 Class)
- 按成员描述符反序列化各字段值(如把 CONSTANT_String_info 转为 Java String)
- 构造一个动态代理实例(JDK 代理实现
Annotation接口),把解析出的键值对作为代理的 handler 数据源
这个过程不依赖注解类本身的字节码逻辑,而是 JVM 内置的 annotation 解析器完成的——所以即使注解类没被加载,只要其二进制签名合法,JVM 仍可构建出代理对象(前提是该注解类后续能成功 resolve)。
自定义注解处理器不改变字节码识别机制
APT(Annotation Processing Tool)在编译期工作,它读取的是 AST(抽象语法树),不是字节码里的 annotation 属性。它不影响 JVM 是否识别注解,只影响是否生成新源码或资源。真正决定 JVM 是否“看到”注解的,始终是 @Retention + 编译器写入 + JVM 加载时解析这三步。
换句话说:只要注解被保留到 class 文件,并符合 JVMS 第 4.7.16 节对 RuntimeVisibleAnnotations 的格式要求,JVM 就能识别它——无需你手动操作字节码,javac 已经按规范完成了。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











