注解本质是继承Annotation接口的特殊接口,其价值依赖JVM元数据存储与反射机制提取:@Retention决定生命周期(SOURCE/CLASS/RUNTIME),RUNTIME级注解通过反射获取并由动态代理实例化,支撑Spring、MyBatis等框架实现零侵入配置。

自定义注解本身不执行逻辑,它的价值在于被工具或框架在特定阶段读取并响应。真正让它“活起来”的,是 JVM 对注解元数据的存储机制 + 反射 API 在运行时的提取能力。
注解本质:不是标签,是带元数据的接口
Java 中每个注解(如 @BusinessLog)在字节码层面就是一个接口,隐式继承 java.lang.annotation.Annotation。反编译后能看到:
public interface BusinessLog extends java.lang.annotation.Annotation
注解里声明的每个属性(比如 String operateName()),就是这个接口里的抽象方法。JVM 不会直接实例化它,而是在运行时用动态代理生成一个实现类,把你在代码中写的值(如 @BusinessLog(operateName = "登录"))作为代理对象的返回值。
立即学习“Java免费学习笔记(深入)”;
注解怎么进字节码:由 @Retention 决定存到哪一步
编译器是否把注解写入 .class 文件、写到什么位置,完全取决于它的 @Retention 策略:
- RetentionPolicy.SOURCE:只保留在源码里,javac 编译完就丢弃(如 @Override)。不会出现在 .class 中,反射当然读不到。
- RetentionPolicy.CLASS:写入 .class 的常量池和属性表,但 JVM 类加载时主动忽略,不进运行时内存。Lombok 注解多走这条路——编译期插桩用完即弃。
- RetentionPolicy.RUNTIME:完整写入 .class,并在类加载时由 JVM 提取进运行时数据结构(比如 AnnotationData 对象),反射 API 才能访问。Spring 的 @Autowired、MyBatis 的 @Select 都必须设为此值。
反射怎么拿到注解:从 Class 到动态代理对象
当你调用 clazz.getAnnotation(BusinessLog.class),背后发生三件事:
- JVM 先检查该 Class 是否已加载其 AnnotationData(缓存了所有 RUNTIME 级注解的解析结果);没缓存则从 .class 的 RuntimeVisibleAnnotations 属性中解析并缓存。
- 找到对应注解条目后,JVM 用动态代理创建一个 BusinessLog 接口的实例,把字节码里存的键值对(如 {"operateName": "登录", "operateType": "QUERY"})映射为代理方法的返回值。
- 你调用 anno.operateName(),实际触发的是代理对象的 InvocationHandler.invoke(),返回预设值——整个过程对开发者透明。
为什么框架都依赖这套机制?
因为它是 JVM 原生支持的、零侵入的元数据绑定方式:
- Spring 启动时扫描所有 @Component 类,靠的就是反射遍历 Class#getDeclaredAnnotations();
- MyBatis 执行 @Select("...") 方法前,先用反射读出 SQL 字符串再交给 SqlSession;
- 甚至 Lombok 的编译期处理,也依赖 javac 的 Annotation Processing Tool (APT) 从源码 AST 中提取注解信息。


















