Java注解本质是继承java.lang.annotation.Annotation的特殊接口,编译后存于class文件RuntimeVisibleAnnotations属性中,运行时通过反射获取代理对象,其可见性由@Retention策略决定。

Java 注解不是语法糖,也不是魔法,它是一套被 JVM 和 Java 工具链共同约定的元数据机制。要真正深入底层剖析其实现机制,关键不在“怎么写注解”,而在于搞清三件事:注解在字节码里长什么样、JVM 加载时怎么保存它、运行时反射怎么拿到它并还原成对象。
注解本质是接口,编译后生成特殊字节码
用 @interface 定义的注解,编译后实际是一个继承 java.lang.annotation.Annotation 的接口。javap 反编译可见:
- 接口中每个方法对应注解的一个属性(如
String value()) - 编译器不会生成实现类,但会在 .class 文件的
RuntimeVisibleAnnotations属性中写入结构化数据 - 该属性以二进制形式记录注解类型、属性名、属性值(含默认值),供 JVM 后续解析
@Retention 决定注解能否被反射读取
生命周期策略直接决定底层是否“可见”:
-
SOURCE:仅存于源码,编译器处理完就丢弃(如
@Override),.class 中无痕迹 -
CLASS:写入 .class,但类加载器不将其加载进内存,
Class.getAnnotations()返回空 -
RUNTIME:写入 .class 且 JVM 在加载类时解析并缓存到
AnnotationData结构中,反射才能访问
运行时获取注解,其实是代理对象调用
当你写 clazz.getAnnotation(MyAnno.class),背后发生的是:
立即学习“Java免费学习笔记(深入)”;
- JVM 先调用
Class.annotationData(),触发懒加载解析RuntimeVisibleAnnotations - 解析结果存入
AnnotationData.annotations(一个Map<class>, Annotation></class>) - 返回的对象并非真实实例,而是
AnnotationInvocationHandler的动态代理 - 每次调用
anno.value(),都走代理的invoke()方法,从内部memberValuesMap 中取值
自定义注解生效的前提是有人“读它”
很多问题不是注解写错了,而是缺少驱动逻辑:
- Spring 的
@Transactional依赖 AOP 切面扫描@Target(RetentionPolicy.RUNTIME)方法 - Lombok 的
@Data在编译期由注解处理器(JSR-269)介入,修改 AST 或字节码 - 纯反射工具需手动遍历类/方法/字段,调用
getAnnotation()或getDeclaredAnnotations()


















