# 注解机制
作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)
Java 注解(Annotation)自 JDK 1.5 引入,是一种元数据机制,用于描述类、方法、字段、参数等信息。
它不仅能被编译器读取,还可以在运行时通过反射访问,广泛用于框架开发、编译期代码生成和运行时逻辑控制。
# 一、注解的本质与生命周期
# 注解本质
Java 注解(Annotation)是 JDK 1.5 引入的一种 元数据机制,用来修饰:
- 类、方法、字段、参数、局部变量
- 注解本身(元注解)
- 模块、包(JDK 9+)
🔧 注解 = 一种 结构化的、可被编译器和运行时读取的标记机制,本质上是接口的语法糖。
# 注解生命周期
| 生命周期 | RetentionPolicy | 是否可反射获取 | 示例 |
|---|---|---|---|
| 源码期 | SOURCE | ❌ | @Override, @SuppressWarnings |
| 编译期 | CLASS | ❌(运行期不可见) | 大部分框架做 APT |
| 运行期 | RUNTIME | ✅(通过反射) | @Autowired, @RequestMapping |
@Retention(RetentionPolicy.RUNTIME)
# 二、注解语法与反射读取
# 自定义注解的结构
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LogOperation {
String module() default "默认模块";
String action();
}
2
3
4
5
6
7
8
@Target:注解能放在哪里(TYPE, METHOD, FIELD...)@Retention:注解存活到何时(SOURCE, CLASS, RUNTIME)@Documented:是否出现在 JavaDoc 中@Inherited:子类是否能继承父类注解(仅对类有效)
# 反射读取注解
SomeService类上使用LogOperation注解
Class<?> clazz = SomeService.class;
Method method = clazz.getMethod("save");
LogOperation anno = method.getAnnotation(LogOperation.class);
System.out.println(anno.action());
2
3
4
# 三、注解底层机制
# 编译期注解处理(APT)
APT(Annotation Processing Tool)是 javac 的插件机制,可以在编译期处理带注解的源代码,并生成 Java 代码、XML 文件等资源。
如何工作?
- 编译器运行时,会查找实现了
javax.annotation.processing.Processor接口的类。 - 调用
process()方法处理代码中标注的注解。 - 使用
Filer生成 Java 源码、类、META-INF 配置等。
常见使用场景:
- Lombok:在编译期生成 getter/setter、toString
- MapStruct:生成属性映射代码
- Dagger、AutoService:代码生成依赖注入逻辑、服务注册表
编译期注解处理器示例(简化版)
自定义注解
@Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface HelloAnnotation { String value() default "Hello"; }1
2
3
4
5自定义注解处理器
@SupportedAnnotationTypes("com.example.HelloAnnotation") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class HelloProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (Element e : roundEnv.getElementsAnnotatedWith(HelloAnnotation.class)) { String className = e.getSimpleName().toString(); String msg = "注解处理器捕捉到了类: " + className; System.out.println(msg); } return true; } }1
2
3
4
5
6
7
8
9
10
11
12
13
14注册处理器
// 创建文件 META-INF/services/javax.annotation.processing.Processor // 内容 com.example.HelloProcessor1
2
3
4编译效果 使用注解后,处理器可输出信息、生成类、做校验。
# 运行时注解的处理流程
当你给类、方法、字段加上 @Retention(RetentionPolicy.RUNTIME) 注解后,JVM 会在加载 class 文件时,把注解信息存储到运行时内存结构中,这使得反射 API 能够读取注解信息。
字节码中结构位置
运行时注解会被编译器保存到 class 文件的 RuntimeVisibleAnnotations 属性表中。
使用
javap -v命令可查看:javap -v MyClass.class # 参考输出样例 RuntimeVisibleAnnotations: 0: #10(@Lcom/example/MyAnnotation;)1
2
3
4
5JVM 如何读取注解(以方法为例)
Method m = MyClass.class.getMethod("myMethod"); MyAnnotation anno = m.getAnnotation(MyAnnotation.class); System.out.println(anno.value());1
2
3底层过程如下:
JVM 加载类时,读取 class 文件中
RuntimeVisibleAnnotations。JVM 构建
AnnotationData对象缓存这些注解。当你通过
getAnnotation()访问时,反射框架直接从缓存中取出。反射返回的注解对象本质上是由 Proxy 动态生成的代理对象。
反射返回的代理对象实际上是 JVM 用 Proxy 动态构建出的实现类。
# 注解处理机制的总结流程图
┌─────────────────────┐
│ Java 注解使用 │
└────────┬────────────┘
↓
┌────────────┐
│ 编译期注解 │───→ APT 编译器插件(Processor)
└────────────┘
↓
┌────────────┐
│ Class 文件 │───→ 注解写入属性表(如 RuntimeVisibleAnnotations)
└────────────┘
↓
┌────────────┐
│ JVM 加载 │───→ 解析注解结构 → Proxy 返回
└────────────┘
↓
┌────────────┐
│ 反射调用 │→ method.getAnnotation()...
└────────────┘
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 四、注解高级特性
# 支持枚举和数组
enum Level { INFO, WARN, ERROR }
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
Level level() default Level.INFO;
String[] tags() default {};
}
2
3
4
5
6
7
8
9
# 嵌套注解
public @interface Permission {
String name();
}
public @interface Permissions {
Permission[] value();
}
2
3
4
5
6
7