RetentionPolicy.CLASS 注解写入字节码但不加载到运行时,仅存于常量池和RuntimeVisible/InvisibleAnnotations属性中,可通过javap、十六进制编辑器或ASM验证,APT等编译期工具依赖其字节码存在性。

RetentionPolicy.CLASS 注解确实写入了字节码,但不会被 JVM 加载进运行时方法区,也不会出现在 Class 对象的反射 API 中。它只存在于 .class 文件的常量池和属性表里,属于“静态可见、动态不可见”的元数据。
CLASS 注解在字节码中的具体位置
编译后,CLASS 级注解会以以下形式嵌入 class 文件:
- Constant Pool(常量池):注解类名、成员名、字面量值(如字符串、数字)作为 UTF8、String、Integer 等常量项存入;
-
attributes(属性表):核心信息保存在
RuntimeVisibleAnnotations或RuntimeInvisibleAnnotations属性中(注意:名称有误导性,实际是否“可见”取决于 RetentionPolicy,而非属性名); -
不触发类加载行为:JVM 解析 class 文件时会读取这些属性,但除非显式调用
Class.getAnnotations()(该方法只返回 RUNTIME 注解),否则不会解析或实例化注解对象。
如何验证它真实存在于字节码中
你可以用标准 JDK 工具直接观察:
- 执行
javap -v YourClass.class,在输出末尾查找RuntimeVisibleAnnotations或RuntimeInvisibleAnnotations属性块,能看到类似@Lcom/example/MyClassAnnotation;的条目; - 用十六进制编辑器打开 .class 文件,搜索 UTF8 常量中你的注解全限定名(如
com/example/MyClassAnnotation),能定位到原始字节; - 用 ASM 或 Javassist 等字节码库遍历
ClassReader,通过visitAnnotation()回调可捕获 CLASS 注解——这正是 APT、Lombok、Dagger 等工具的工作原理。
为什么 CLASS 级注解不能被反射读取
关键不在字节码是否存在,而在 JVM 规范的设计约定:
-
Class.getAnnotations()和Method.getAnnotation(...)等反射方法,底层只扫描RuntimeVisibleAnnotations属性中 且 对应注解类声明了@Retention(RetentionPolicy.RUNTIME)的条目; - 即使你手动把一个 SOURCE 注解强行写进字节码(绕过 javac),只要其定义是
@Retention(SOURCE),JVM 也不会将其纳入运行时注解集合; - CLASS 是一种“中间态”保留策略:它为构建期工具提供结构化元数据,又避免运行时开销与内存占用。
典型使用场景依赖的就是这种字节码存在性
很多框架或工具并不依赖反射,而是直接操作字节码或编译产物:
- APT(Annotation Processing Tool)在编译期扫描
@Retention(CLASS)注解,生成配套 Java 文件(如 ButterKnife 的ViewBinder); - ProGuard / R8 压缩混淆时,会识别并保留 CLASS 注解中的关键字段(如
@Keep),因为它们在字节码里可查; - Android Data Binding 编译器读取布局中绑定表达式的注解,依赖的正是 CLASS 级注解在 class 文件中的稳定存在。









