✅ 简洁回答:
是的,编译器会把注解信息(包括注解类型和参数值)存储在被注解类的 .class
文件的特殊结构中。
JVM 运行时可以通过 Class.getAnnotation()
方法反射出一个动态代理对象,它实现了注解接口,封装了你在源码中定义的值。
🧠 注解反射的关键机制:Annotation Proxy(注解代理类)
- 注解是接口;
- 你不能直接用
new
创建它的实例; - JVM 会使用动态代理(
Proxy.newProxyInstance(...)
)自动创建一个实现了注解接口的对象; - 这个对象内部封装了注解的值,像
report.name()
实际就是调用了这个代理对象中的方法。
🔍 编译器是如何处理注解的?
1. 当你这样写:
@Report(name = "年度报告", type = 1)
public class Person {}
编译器会做两件事:
- 把
@Report
这个注解的“存在”信息写入Person.class
的 class 文件中 - 把注解的参数(比如 name=“年度报告”, type=1)一并写进去
这些信息存储在 .class
文件的特殊区域里,叫:
RuntimeVisibleAnnotations
或RuntimeInvisibleAnnotations
你可以用 javap -v Person.class
看到这部分:
RuntimeVisibleAnnotations:
0: #19(#20=s#21, #22=I#3)
Report(name="年度报告", type=1)
🔍 运行时怎么获取注解?
当你运行这句代码时:
Report report = Person.class.getAnnotation(Report.class);
JVM 会:
- 读取
Person.class
中的RuntimeVisibleAnnotations
部分; - 确认里面有
@Report
; - 提取所有注解参数;
- 利用动态代理(Java Proxy 机制)生成一个实现了
Report
接口的代理对象; - 返回这个代理对象,供你调用
report.name()
、report.type()
等方法。
✅ 类似结构图
┌─────────────────────┐
│ Person.class │
│─────────────────────│
│ 字节码部分 │
│ RuntimeVisibleAnnotations: ← 被编译器写入
│ → @Report(name="...", ...) │
└─────────────────────┘
↓
JVM反射
↓
动态创建代理对象(实现 Report 接口)
↓
report.name() 实际调用 Proxy.invoke()
✅ 核心总结
步骤 | 说明 |
---|---|
注解写在类上 | 编译器将注解及其参数存入 .class 文件 |
使用反射获取注解 | JVM 读取 class 文件中注解数据 |
生成代理对象 | JVM 自动创建一个实现了注解接口的代理类实例 |
使用注解参数 | 调用 report.name() 实际是调用代理对象的对应方法返回注解值 |
✅ 结尾补充:是不是引用?
你的提问中说:
“是不是编译器会自动将注解的引用或是什么放入到被注解修饰过的类中?”
这个说法基本正确,不过更准确说:
- 编译器不是把注解对象的“引用”放进去(因为注解在运行时才被生成);
- 而是把注解的类型信息 + 参数值写进了 class 文件中的 注解表(属性表结构);
- JVM 运行时用这些信息去动态生成注解实现对象(代理)。