java元注解是编译期与运行时协同的契约规则:编译器按@retention/@target等生成字节码元数据,jvm按保留策略加载,框架通过反射读取预解析结果驱动逻辑,全程不修改字节码。

Java元注解不是“语法糖”,而是编译期与运行时协同工作的契约规则。它的底层机制核心在于:编译器按元注解声明生成字节码元数据,JVM按保留策略加载,框架通过反射读取并触发逻辑——整个过程不涉及类加载后的代码修改,纯靠元数据驱动。
编译阶段:元注解决定注解如何写入字节码
当你定义一个自定义注解(如@MyApi)并用@Retention(RetentionPolicy.RUNTIME)修饰时,javac 编译器会做三件事:
- 验证该注解是否符合@Target限定的使用位置(比如只允许标在方法上,却用在字段上,编译直接报错);
- 将注解声明及其属性值(如@MyApi(path = "/user"))以结构化形式写入.class文件的
RuntimeVisibleAnnotations属性表; - 若使用@Documented,则在生成 Javadoc 时把该注解名加入文档描述段;若用@Inherited,则在字节码中设置
ACC_ANNOTATION标志位并影响子类继承逻辑。
@Retention 是字节码存留的开关
@Retention不是“告诉 JVM 怎么做”,而是“告诉编译器往哪存”:
- SOURCE:仅保留在源码,javac 解析完就丢弃(如@Override,编译检查后不留痕迹);
- CLASS:写入.class但不加载进 JVM 方法区(Lombok 的@Data依赖此阶段,由注解处理器在编译期生成 getter/setter 字节码);
-
RUNTIME:完整写入.class,并在类加载时由 JVM 加载到运行时常量池,后续可通过
Class.getAnnotation()反射读取。
@Target 控制的是编译器校验边界
它本质是一组白名单枚举(ElementType.METHOD、ElementType.TYPE等),javac 在解析源码时逐个比对被标注元素的 AST 节点类型。一旦越界,立即中断编译并报错:error: annotation @XXX is not applicable to this kind of declaration。这不是运行时检查,也不依赖反射——它发生在词法分析之后、字节码生成之前。
运行时读取:反射 API 实际调用的是 JVM 的注解解析器
当代码执行method.getAnnotation(MyApi.class)时,JVM 并非“现场扫描源码”,而是:
- 从该
Method对象关联的java.lang.reflect.Method实例中,取出已缓存的AnnotationData结构; - 该结构在类加载阶段就从
RuntimeVisibleAnnotations表解析完毕,包含注解类型、属性名、属性值(已反序列化为 Java 对象); - 反射调用只是返回这个预解析结果,不触发额外字节码解析——所以@Retention(RUNTIME)是反射可用的前提,但不是每次调用都重新读字节码。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











