[Java] 浅析注解是如何实现的

154 阅读7分钟

浅析 java 中的注解是如何实现的

大家在日常的工作中,应该已经用过注解(Annotation)了,那么你有没有思考过注解到底是如何实现的呢?本文对此进行(浅层次的)分析。

结论

  1. 每个注解都 extendjava.lang.annotation.Annotation
  2. 每个注解都是 interface
  3. 注解用到了动态代理,它所用到的 InvocationHandler 的实现类是AnnotationInvocationHandler

我画了个简单的思维导图 ⬇️

mindmap
      root)注解(
          每个注解都是 interface
            每个注解都 extend 了 java.lang.annotation.Annotation<br/>(⬆️ 它也是接口)
            获取注解的数据时,会用到动态代理,<br/>对应的 InvocationHandler 是<br/> AnnotationInvocationHandler
    

正文

代码及准备工作

代码

我写了如下的代码 ⬇️ 来论证上面的三个结论,请将代码保存为 AnnotationStudy.java

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@MyAnnotation(func1 = "actual value for func1()", func3 = "actual value for func3()")
public class AnnotationStudy {
    public static void main(String[] args) {
        MyAnnotation myAnnotation = AnnotationStudy.class.getAnnotation(MyAnnotation.class);
        System.out.println("MyAnnotation is an interface: " + MyAnnotation.class.isInterface());
        System.out.println("The name of myAnnotation's class is: " + myAnnotation.getClass().getName());

        System.out.println("The return value of the \"func1()\" method is: " + myAnnotation.func1());
        System.out.println("The return value of the \"func2()\" method is: " + myAnnotation.func2());
        System.out.println("The return value of the \"func3()\" method is: " + myAnnotation.func3());
    }
}

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    String func1() default "placeholder for func1()";
    String func2() default "placeholder for func2()";
    String func3();
}
MyAnnotation 注解中的 3 个方法

MyAnnotation 类中定义了如下的 3 个方法

方法是否有默认值AnnotationStudy 上是否显式提供了对应的值
func1()
func2()
func3()

javac AnnotationStudy.java 命令可以编译 AnnotationStudy.java。 编译后,会生成如下两个 class 文件

  • AnnotationStudy.class
  • MyAnnotation.class

执行 java AnnotationStudy 命令后,会看到如下的结果

MyAnnotation is an interface: true
The name of myAnnotation's class is: $Proxy1
The return value of the "func1()" method is: actual value for func1()
The return value of the "func2()" method is: placeholder for func2()
The return value of the "func3()" method is: actual value for func3()

javap -v -p AnnotationStudy 命令可以查看 AnnotationStudy.class 的内容。

完整的结果有一百多行,这里就不展示了。 结果中有如下几行 ⬇️

RuntimeVisibleAnnotations:
  0: #67(#50=s#68,#58=s#69)
    MyAnnotation(
      func1="actual value for func1()"
      func3="actual value for func3()"
    )

从这几行可以看出,我们代码中的第 4 行,即下面这行的信息,会保存在 AnnotationStudy.class 文件的 RuntimeVisibleAnnotations 属性中。

@MyAnnotation(func1 = "actual value for func1()", func3 = "actual value for func3()")

关于 RuntimeVisibleAnnotations 属性的结构, Java Virtual Machine Specification 中的 4.7.16. The RuntimeVisibleAnnotations Attribute 小节 有相关介绍,这里就不展开了。其中提到了下面的内容 ⬇️

The RuntimeVisibleAnnotations attribute stores run-time visible annotations on the declaration of the corresponding class, field, method, or record component.

那么 MyAnnotation 注解中,func1()func2() 的默认值保存在了哪里呢? 我们用 javap -v -p MyAnnotation 命令可以查看 MyAnnotation.class 的内容。 部分结果如下(常量池等部分略)⬇️

{
  public abstract java.lang.String func1();
    descriptor: ()Ljava/lang/String;
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
    AnnotationDefault:
      default_value: s#10
        "placeholder for func1()"

  public abstract java.lang.String func2();
    descriptor: ()Ljava/lang/String;
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
    AnnotationDefault:
      default_value: s#12
        "placeholder for func2()"

  public abstract java.lang.String func3();
    descriptor: ()Ljava/lang/String;
    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "AnnotationStudy.java"
RuntimeVisibleAnnotations:
  0: #17(#18=e#19.#20)
    java.lang.annotation.Retention(
      value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME
    )

javap 的处理结果可以看出,

  • func1()/func2()/func3() 都是抽象(abstract)方法
  • func1()/func2() 的默认值保存在了 AnnotationDefault 属性中

Java Virtual Machine Specification 中的 4.7.22. The AnnotationDefault Attribute 中提到了 AnnotationDefault 属性的结构,这里不展开说。 其中提到了以下内容 ⬇️

The AnnotationDefault attribute records the default value (JLS §9.6.2) for the element represented by the method_info structure.

准备工作已经做好了,我们现在来验证前文提到的那 3 个结论。

结论 1: 每个注解都 extendjava.lang.annotation.Annotation

java.lang.annotation.Annotation 是一个接口(interface), 从 Annotation.java 中可以看到 Annotation 接口的 javadoc ⬇️

image.png

其中提到了所有的注解都会 extend java.lang.annotation.Annotation 这一接口。

我们从 class 文件层面验证一下。

javap -v -p MyAnnotation 命令可以查看 MyAnnotation.class 的内容。 它的部分内容如下 ⬇️

interface MyAnnotation extends java.lang.annotation.Annotation
  minor version: 0
  major version: 66
  flags: (0x2600) ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION

这几行里的第一行是 ⬇️

interface MyAnnotation extends java.lang.annotation.Annotation

由此可见 MyAnnotation 确实 extendjava.lang.annotation.Annotation。 我画了个简单的类图来表示两者的关系 ⬇️

mermaid-diagram-2025-08-27-193342.png

结论 2: 每个注解都是 interface

Java Language Specification 中的 9.6. Annotation Interfaces 小节 的开头提到 ⬇️

An annotation interface declaration specifies an annotation interface, a specialized kind of interface. To distinguish an annotation interface declaration from a normal interface declaration, the keyword interface is preceded by an at sign (@).

由此可见,每个注解都是 interface

除此之外,我们也可以从 class 文件的内容来着手。

javap -v -p MyAnnotation 的结果中,有如下的内容 ⬇️

interface MyAnnotation extends java.lang.annotation.Annotation
  minor version: 0
  major version: 66
  flags: (0x2600) ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION

这里的第 4 行有个 flags,这个 flags 是指什么呢? Java Virtual Machine Specification 中的 4.1. The ClassFile Structure 小节 里提到了 class 文件的结构 ⬇️

image.png

我们刚刚说的 flags 其实就是 class 文件中的 access_flags

flags 的值为 0x2600,而 0x2600 = 0x2000 + 0x0400 + 0x0200,所以一共有 3flag 被置位。用大白话说,就是有 3flag 的值不是 0。 这 3flag 分别是 ⬇️

  • ACC_ANNOTATION: 0x2000
  • ACC_ABSTRACT: 0x0400
  • ACC_INTERFACE: 0x0200

但是为什么这 3flag 会被置位呢? 在 Java Virtual Machine Specification 中的 4.1. The ClassFile Structure 小节 里可以找到原因(重要的部分我用红线标出来了)⬇️ image.png

由此可见,对任意的注解而言,其 class 文件中的 ACC_ANNOTATION/ACC_ABSTRACT/ACC_INTERFACE3flag 都应该被置位。 所以 class 文件的内容也可以辅助证明所有注解都是 interface 这一论断。

结论 3: 注解用到了动态代理,它所用到的 InvocationHandler 的实现类是AnnotationInvocationHandler

在使用 MyBatis 时,可以定义 mapper interface。当我们借助 mapper interface 的实例来执行 SQL 语句时,这些实例自然是来自某个/某些实现类。与此类似,因为所有注解都是接口(interface),所以当我们获取到接口的某个实例时,这个实例必然属于某个实现类。

先提几个问题 ⬇️

  • 注解背后的实现类是什么?
  • 注解是否用到了动态代理?
  • 如果注解用到了动态代理,那么注解所使用的 InvocationHandler 的实现类是什么?

带着这几个问题,我们再看一下上文的代码的运行结果(下图是在 Intellij IDEA 中运行的结果,在命令行执行 java AnnotationStudy 也可以看到这样的输出)

image.png

从输出中可以看到,MyAnnotation 的实例 myAnnotation 的类型是 $Proxy1。奇怪,这是什么?我们并没有定义名叫 $Proxy1class。我们在这里(也就是代码的第 9 行)打个断点,看看发生了什么。Debug 后,会看到 myAnnotation 这个实例中有个名为 h 的字段 ⬇️

image.png

借助 Intellij IDEA,可以看到 h 的精确类型是 sun.reflect.annotation.AnnotationInvocationHandler ⬇️

image.png

AnnotationInvocationHandler.java 中可以看到sun.reflect.annotation.AnnotationInvocationHandler 这个类的源码。

先画张简单的类图 ⬇️

mermaid-diagram-2025-08-28-112358.png

注意:在上面的类图中,InvocationHandler 中的 invoke(...) 方法会 throws Throwable,但我不知道类图中应该如何展示 throws Throwable,所以就把它省略了。

从类图中可以看到,AnnotationInvocationHandlerimplementInvocationHandler 这个接口,而我们在使用 JDK 的动态代理时,需要提供 InvocationHandler 的实现类。 这样看来注解的实现用到了 JDK 的动态代理,而 AnnotationInvocationHandler 就是注解所用到的 InvocationHandler 的实现类。

不过到这里只是推测,还需要验证一下。

如果这里用到了动态代理的话,那么执行如下的命令将会生成代理类的 class 文件。

java -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true AnnotationStudy

至于为什么要将 jdk.proxy.ProxyGenerator.saveGeneratedFiles 的值置为 true,可以参考 ProxyGenerator.java 中的 第 106 行第 213 行 以及相关的逻辑,这里就不展开说了。另外如果读者朋友对 JDK 的动态代理不太熟悉的话,可以另找文章看一看,本文不讨论其中的细节。

执行 java -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true AnnotationStudy 命令后,会看到以下的 class 文件(另外还会生成一个名为 jdk 的目录,这里忽略)

  • $Proxy1.class
  • AnnotationStudy.class
  • MyAnnotation.class

和之前的结果相比,多了 $Proxy1.class

我们用 javap -v -p '$Proxy1' 命令可以查看 $Proxy1.class 的内容。 完整的内容比较长,我们可以用 javap -p '$Proxy1' 查看简略的结果 ⬇️

final class $Proxy1 extends java.lang.reflect.Proxy implements MyAnnotation {
  private static final java.lang.reflect.Method m0;
  private static final java.lang.reflect.Method m1;
  private static final java.lang.reflect.Method m2;
  private static final java.lang.reflect.Method m3;
  private static final java.lang.reflect.Method m4;
  private static final java.lang.reflect.Method m5;
  private static final java.lang.reflect.Method m6;
  public $Proxy1(java.lang.reflect.InvocationHandler);
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public final java.lang.String toString();
  public final java.lang.String func1();
  public final java.lang.String func2();
  public final java.lang.String func3();
  public final java.lang.Class annotationType();
  static {};
  private static java.lang.invoke.MethodHandles$Lookup proxyClassLookup(java.lang.invoke.MethodHandles$Lookup) throws java.lang.IllegalAccessException;
}

基于 javap 命令提供的结果,可以画出如下的类图 ⬇️ (类图中省略了一些字段/方法)

mermaid-diagram-2025-08-28-111938.png

注意:就本文的例子而言,上图中 java.lang.reflect.Proxy 类里的 h 字段的精确类型是 sun.reflect.annotation.AnnotationInvocationHandler。但在其他使用动态代理的场景中,h 的精确类型可能会是别的实现类。

在上方的类图中,可以看到 AnnotationInvocationHandler 类中有 typememberValues 字段

照理说,AnnotationInvocationHandler 的构造函数中应该会有些预处理的逻辑,而它的invoke(Object proxy, Method method, Object[] args) 方法中应该会有如何分派方法的逻辑(比如 MyAnnotation 中有 value() 方法,那么 value() 方法的返回值应该是在 invoke(...) 方法中处理好的)。于是我们就有了如下的任务列表 ⬇️

  • AnnotationInvocationHandler 的构造函数
  • AnnotationInvocationHandlerinvoke(Object proxy, Method method, Object[] args) 方法
AnnotationInvocationHandler 的构造函数

我们可以在 AnnotationInvocationHandler 构造函数的入口处打个断点,然后 debug。 下图展示了断点的位置。 image.png

开始 debug 后,需要观察 type 的值,因为我们暂时只关心 MyAnnotation 的情形,所以当 type 是其他值时,可以继续。 下图展示了当 typeMyAnnotation 时,相关值的具体内容 ⬇️

image.png

从上图可见,当进入 AnnotationInvocationHandler 的构造函数时

  • 参数 typeMyAnnotation 对应的 class 对象
  • 参数 memberValues 是一个 LinkedHashMap,其中存储了 MyAnnotation 注解中 方法名 -> 返回值 的映射关系,具体的映射关系如下表所示 ⬇️
方法名返回值
func1"actual value for func1()"
func2"placeholder for func2()"
func3"actual value for func3()"

构造函数中的逻辑比较直观,它会

  • 进行基本的参数检查
  • type 参数给 this.type 字段赋值
  • memberValues 参数给 this.memberValues 字段赋值

这里有个问题,this.memberValues 中保存了 方法名 -> 返回值 的映射关系,那么如果注解中定义了重载的方法,例如下面这样 ⬇️ 那要怎么保存这种映射关系呢?

public @interface AFakeAnnotation {
  String function1(int a);
  String function1(int a, int b);
}

不必担心,因为上面的这个 AFakeAnnotation 是不合法的,编译时会有报错。 在注解中定义的方法,有不少限制,限制之一是这些方法不允许带有参数。既然注解中定义的方法都不能带有参数,那么在注解中也就不可能存在重载方法了。 Java Language Specification 中的 9.6.1. Annotation Interface Elements 小节 对注解中定义的方法的限制有如下的描述 ⬇️

By virtue of the grammar above, a method declaration in an annotation interface declaration cannot have formal parameters, type parameters, or a throws clause; and cannot be privatedefault, or static. Thus, an annotation interface cannot have the same variety of methods as a normal interface. Note that it is still possible for an annotation interface to inherit a default method from its implicit superinterface, java.lang.annotation.Annotation, though no such default method exists as of Java SE 24.

这些内容在下图蓝色方框的位置 ⬇️ image.png

虽然在注解中定义的方法都不能带有参数,但注解也是 Object,所以注解中会有来自 Object 类的 equals(Object) 方法。这样说来,我们还是可以写出下面这样的(古怪的)注解,而它的 equals() 方法和 Object 中的 equals(Object) 方法构成了重载的关系。

public @interface WeirdAnnotation {
  boolean equals();
}

不过即使这样,this.memberValues 也还是能正常运转,下文马上会解释具体的原因。

小结

AnnotationInvocationHandler 的构造函数会

  • 将注解对应的 class 对象(例如 MyAnnotation.class)保存在 this.type 字段中
  • 方法名 -> 返回值 的映射关系保存在 this.memberValues 字段中(这个字段是 Map<String, Object> 类型的

任务列表更新后如下 ⬇️

  • AnnotationInvocationHandler 的构造函数
  • AnnotationInvocationHandlerinvoke(Object proxy, Method method, Object[] args) 方法
AnnotationInvocationHandlerinvoke(Object proxy, Method method, Object[] args) 方法

我们可以在 invoke(...) 方法的入口处打个断点,然后 debug。 下图展示了断点的位置 ⬇️

image.png

开始 debug 后,需要观察 method 参数的值,因为我们暂时只关心 MyAnnotation 的情形,所以当 method 参数与 MyAnnotation 无关时,可以继续。 当执行到 MyAnnotation 中的 func1() 方法时,断点的具体情况如下图所示 ⬇️

image.png

我们来看看 invoke(...) 方法里大致做了些什么

  1. 方法名member 这个局部变量赋值
  2. 找到对应的返回值
    1. 如果 method 参数对应 Object 中的 equals(Object) 方法,则进入方框 1 (见下图)
    2. 如果 method 参数对应 Object 中的 toString() 方法,则进入方框 2 (见下图)
    3. 如果 method 参数对应 Object 中的 hashCode() 方法,则进入方框 3 (见下图)
    4. 如果 method 参数对应 Annotation 中的 annotationType() 方法,则进入方框 4 (见下图),其实就是直接将 this.type 返回
    5. 如果上述的 i/ii/iii/iv都不成立,那么 method 就是这个注解中定义的方法,进入方框 5(见下图),由于 this.memberValues 中保存了 方法名 -> 返回值 的映射关系,所以通过查询 this.memberValues 就知道对应的返回值是什么了(其实方框 5 之后还有点逻辑,这里略)。

image.png

如果我们定义一个古怪的注解 ⬇️ 那么它的 equals() 方法会在上图中的方框 5 中处理(而不是在方框 1 中处理)。

public @interface WeirdAnnotation {
  boolean equals();
}
一个例子

关于 invoke(...) 方法中的逻辑的解释,可能还是有点抽象,我把一段代码的时序图画出来,方便大家验证/思考。 我以 myAnnotation.func1() 为例,用时序图来分析背后的逻辑(myAnnotation.func1() 的位置如下图所示 ⬇️)

image.png

sequenceDiagram
participant m as main(...) method in AnnotationStudy
participant p as $Proxy1 instance
participant h as h
participant mv as memberValues
m ->> p: func1()
Note right of p: m3 is a method object for <br/>func1() method in MyAnnotation ⬇️
p ->> h: invoke(this, m3, null)

h ->> mv: get("func1")
Note right of mv: ⬅️ memberValues is a Map<String, Object> field in h
mv -->> h: return "actual value for func1()"
h -->> p: return "actual value for func1()"
p -->> m: return "actual value for func1()"

注意

  1. 这张时序图中只涉及 main(...) 方法里的 myAnnotation.func1()
  2. 这张时序图中只画了各个方法的主要逻辑。参数检查,异常处理之类的逻辑都没画。

上方时序图的解释

  • m3 是一个 Method 对象,它和 MyAnnotation 中的 func1() 方法对应
  • h 的精确类型是 AnnotationInvocationHandler,所以 h 里有 memberValues 字段
  • $Proxy1 implementMyAnnotation 接口,所以可以通过 $Proxy1 的实例来调用 func1() 方法 (MyAnnotation 中定义了 func1() 方法),Intellij IDEA 中所展示的 $Proxy1.class 中的 func1() 方法是这样的 ⬇️
public final String func1() {
    try {
        return (String)super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}

至于为何 func1() 会与 m3 对应,我们看一下完整的对应关系 ⬇️ (对理解注解的实现机制而言,这些对应关系的顺序并不重要)

代理类 $Proxy1 中的方法代理类 $Proxy1 中,与之对应的 Method 字段是什么
hashCode() (来自 Object 类)m0
equals(Object) (来自 Object 类)m1
toString() (来自 Object 类)m2
func1()m3
func2()m4
func3()m5
annotationType() (来自 Annotation 类)m6
小结

AnnotationInvocationHandlerinvoke(Object proxy, Method method, Object[] args) 方法中,会

  • 找到和 method 参数对应的返回值
    • 如果 method 和以下 4 个方法中的某一个对应,则进入相应的处理逻辑
      1. Object.equals(Object)
      2. Object.toString()
      3. Object.hashCode()
      4. Annotation.annotationType()
    • 否则,用 this.memberValues 查询和 method 对应的返回值

任务列表更新后如下 ⬇️

  • AnnotationInvocationHandler 的构造函数
  • AnnotationInvocationHandlerinvoke(Object proxy, Method method, Object[] args) 方法

正文完。

参考资料