# 注解机制

作者: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)
1

# 二、注解语法与反射读取

# 自定义注解的结构

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LogOperation {
    String module() default "默认模块";
    String action();
}
1
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());
1
2
3
4

# 三、注解底层机制

# 编译期注解处理(APT)

APT(Annotation Processing Tool)是 javac 的插件机制,可以在编译期处理带注解的源代码,并生成 Java 代码、XML 文件等资源

如何工作?

  1. 编译器运行时,会查找实现了 javax.annotation.processing.Processor 接口的类。
  2. 调用 process() 方法处理代码中标注的注解。
  3. 使用 Filer 生成 Java 源码、类、META-INF 配置等。

常见使用场景:

  • Lombok:在编译期生成 getter/setter、toString
  • MapStruct:生成属性映射代码
  • Dagger、AutoService:代码生成依赖注入逻辑、服务注册表

编译期注解处理器示例(简化版)

  1. 自定义注解

    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.TYPE)
    public @interface HelloAnnotation {
        String value() default "Hello";
    }
    
    1
    2
    3
    4
    5
  2. 自定义注解处理器

    @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
  3. 注册处理器

    // 创建文件
    META-INF/services/javax.annotation.processing.Processor
    // 内容
    com.example.HelloProcessor
    
    1
    2
    3
    4
  4. 编译效果 使用注解后,处理器可输出信息、生成类、做校验。

# 运行时注解的处理流程

当你给类、方法、字段加上 @Retention(RetentionPolicy.RUNTIME) 注解后,JVM 会在加载 class 文件时,把注解信息存储到运行时内存结构中,这使得反射 API 能够读取注解信息。

  1. 字节码中结构位置

    运行时注解会被编译器保存到 class 文件的 RuntimeVisibleAnnotations 属性表中。

    使用 javap -v 命令可查看:

    javap -v MyClass.class
    
    # 参考输出样例
    RuntimeVisibleAnnotations:
      0: #10(@Lcom/example/MyAnnotation;)
    
    1
    2
    3
    4
    5
  2. JVM 如何读取注解(以方法为例)

    Method m = MyClass.class.getMethod("myMethod");
    MyAnnotation anno = m.getAnnotation(MyAnnotation.class);
    System.out.println(anno.value());
    
    1
    2
    3

    底层过程如下:

    1. JVM 加载类时,读取 class 文件中 RuntimeVisibleAnnotations

    2. JVM 构建 AnnotationData 对象缓存这些注解。

    3. 当你通过 getAnnotation() 访问时,反射框架直接从缓存中取出。

    4. 反射返回的注解对象本质上是由 Proxy 动态生成的代理对象

      反射返回的代理对象实际上是 JVM 用 Proxy 动态构建出的实现类。

# 注解处理机制的总结流程图

┌─────────────────────┐
│   Java 注解使用     │
└────────┬────────────┘
         ↓
     ┌────────────┐
     │ 编译期注解 │───→ APT 编译器插件(Processor)
     └────────────┘
         ↓
     ┌────────────┐
     │ Class 文件 │───→ 注解写入属性表(如 RuntimeVisibleAnnotations)
     └────────────┘
         ↓
     ┌────────────┐
     │  JVM 加载  │───→ 解析注解结构 → Proxy 返回
     └────────────┘
         ↓
     ┌────────────┐
     │  反射调用  │→ method.getAnnotation()...
     └────────────┘

1
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 {};
}

1
2
3
4
5
6
7
8
9

# 嵌套注解

public @interface Permission {
    String name();
}

public @interface Permissions {
    Permission[] value();
}
1
2
3
4
5
6
7