浅析 java 中的注解是如何实现的
大家在日常的工作中,应该已经用过注解(Annotation)了,那么你有没有思考过注解到底是如何实现的呢?本文对此进行(浅层次的)分析。
结论
- 每个注解都
extend了java.lang.annotation.Annotation - 每个注解都是
interface - 注解用到了动态代理,其所用到的
InvocationHandler实现类是AnnotationInvocationHandler
正文
代码及准备工作
我写了些代码(如下)用于论证上面的三个结论,请将下方的代码保存为 AnnotationStudy.java。
java
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@MyAnnotation("some arbitrary value")
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 \"value\" method is: " + myAnnotation.value());
}
}
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String value() default "placeholder";
}
javac AnnotationStudy.java 命令可以编译 AnnotationStudy.java。 编译后,会生成如下两个 class 文件
AnnotationStudy.classMyAnnotation.class
执行 java AnnotationStudy 命令后,会看到如下的结果

结论 1: 每个注解都 extend 了 java.lang.annotation.Annotation
java.lang.annotation.Annotation 是一个接口(interface), 从 Annotation.java 中可以看到 Annotation 接口的 javadoc ⬇️

其中提到了所有的注解都会 extend java.lang.annotation.Annotation 这一接口。
我们从 class 文件层面验证一下。 用 javap -v -p MyAnnotation 命令可以查看 MyAnnotation.class 的内容。 它的主要内容如下图所示 ⬇️ 其中比较重要的部分,我用红线标出来了。

上图中的第一行是
interface MyAnnotation extends java.lang.annotation.Annotation
由此可见 MyAnnotation 确实 extend 了 java.lang.annotation.Annotation。 我画了个简单的类图来表示两者的关系 ⬇️

结论 2: 每个注解都是 interface
Java Language Specification 中的 9.6. Annotation Interfaces 小节 的开头提到 ⬇️
An annotation interface declaration specifies an annotation interface, a specialized kind of interface.
由此可见,每个注解都是 interface。
除此之外,我们也可以从 class 文件的内容来着手。 在 javap -v -p MyAnnotation 的结果的前几行中,有如下的内容
text
flags: (0x2600) ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
这里 flags 是指什么呢? Java Virtual Machine Specification 中的 4.1. The ClassFile Structure 小节 里提到了 class 文件的结构 ⬇️

我们刚刚说的 flags 其实就是 class 文件中的 access_flags。
flags 的值为 0x2600,而 0x2600 = 0x2000 + 0x0400 + 0x0200,所以一共有 3 个 flag 被置位。用大白话说,就是有 3 个 flag 的值不是 0。 这 3 个 flag 分别是 ⬇️
ACC_ANNOTATION:0x2000ACC_ABSTRACT:0x0400ACC_INTERFACE:0x0200
但是为什么这 3 个 flag 会被置位呢? 在 Java Virtual Machine Specification 中的 4.1. The ClassFile Structure 小节 里可以找到原因(重要的部分我用红线标出来了)⬇️ 
由此可见,对任意的注解而言,其 class 文件中的 ACC_ANNOTATION/ACC_ABSTRACT/ACC_INTERFACE 这 3 个 flag 都应该被置位。 所以 class 文件的内容也可以辅助证明所有注解都是 interface 这一论断。
结论 3: 注解用到了动态代理,其所用到的 InvocationHandler 实现类是 AnnotationInvocationHandler
在使用 MyBatis 时,可以定义 mapper interface。当我们借助 mapper interface 的实例来执行 SQL 语句时,这些实例自然是来自某个/某些实现类。与此类似,因为所有注解都是接口(interface),所以当我们获取到接口的某个实例时,这个实例必然属于某个实现类。
注解背后的实现类是什么呢? 注解是否用到了动态代理呢? 如果是的话,注解所使用的 InvocationHandler 的实现类是什么呢?
带着这几个问题,我们再看一下上文的代码的运行结果(下图是在 Intellij IDEA 中运行的结果,在命令行执行 java AnnotationStudy 也可以看到这样的输出)

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

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

在 AnnotationInvocationHandler.java 中可以看到sun.reflect.annotation.AnnotationInvocationHandler 这个类的源码。
先画张简单的类图 ⬇️

注意:在上面的类图中,InvocationHandler 中的 invoke(...) 方法会 throws Throwable,但我不知道类图中应该如何展示 throws Throwable,所以就把它省略了。
从类图中可以看到,AnnotationInvocationHandler 类 implement 了 InvocationHandler 这个接口,而我们在使用 JDK 的动态代理时,需要提供 InvocationHandler 的实现类。 这样看来注解的实现用到了 JDK 的动态代理,而 AnnotationInvocationHandler 就是注解所用到的 InvocationHandler 的实现类。
不过到这里只是推测,还需要验证一下。
如果这里用到了动态代理的话,那么执行如下的命令将会生成代理类的 class 文件。
bash
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.classAnnotationStudy.classMyAnnotation.class
和之前的结果相比,多了 $Proxy1.class。
我们用 javap -v -p '$Proxy1' 命令可以查看 $Proxy1.class 的内容。 完整的内容比较长,如果只看简略内容的话,可以改为 javap -p '$Proxy1',这个命令的结果如下 ⬇️
text
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;
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 value();
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 命令提供的结果,可以画出如下的类图 ⬇️ (类图中省略了一些字段/方法)

注意:就本文的例子而言,上图中 java.lang.reflect.Proxy 类里的 h 字段的精确类型是 sun.reflect.annotation.AnnotationInvocationHandler。但在其他使用的动态代理的场景中,h 的精确类型可能会是其他的实现类。
在上方的类图中,可以看到 AnnotationInvocationHandler 类中有 type 和 memberValues 字段
照理说,AnnotationInvocationHandler 的构造函数中应该会有些预处理的逻辑,而它的invoke(Object proxy, Method method, Object[] args) 方法中应该会有如何分派方法的逻辑(比如 MyAnnotation 中有 value() 方法,那么 value() 方法的返回值应该是在 invoke(...) 方法中处理好的)。于是我们就有了如下的任务列表 ⬇️
-
AnnotationInvocationHandler的构造函数 -
AnnotationInvocationHandler的invoke(Object proxy, Method method, Object[] args)方法
AnnotationInvocationHandler 的构造函数
我们可以在 AnnotationInvocationHandler 构造函数的入口处打个断点,然后 debug。 下图展示了断点的位置。 
开始 debug 后,需要观察 type 的值,因为我们暂时只关心 MyAnnotation 的情形,所以当 type 是其他值时,可以继续。 下图展示了当 type 是 MyAnnotation 时,相关值的具体内容 ⬇️ 
从上图可见,当进入 AnnotationInvocationHandler 的构造函数时
- 参数
type是MyAnnotation对应的class对象 - 参数
memberValues是一个LinkedHashMap,其中存储了MyAnnotation注解中方法名到返回值的映射关系
构造函数中的逻辑比较直观,它会
- 进行基本的参数检查
- 用
type参数给this.type字段赋值 - 用
memberValues参数给this.memberValues字段赋值
小结
在 AnnotationInvocationHandler 的构造函数,会
- 将注解对应的
class对象(例如MyAnnotation.class)保存在this.type字段中 - 将 方法名 -> 返回值 的映射关系保存在
this.memberValues字段中(这个字段是Map<String, Object>类型的,例如"value"->"some arbitrary value"可以是这个map中的一个entry)
任务列表更新后如下 ⬇️
-
AnnotationInvocationHandler的构造函数 -
AnnotationInvocationHandler的invoke(Object proxy, Method method, Object[] args)方法
AnnotationInvocationHandler 的 invoke(Object proxy, Method method, Object[] args) 方法
我们可以在 invoke(...) 方法的入口处打个断点,然后 debug。 下图展示了断点的位置 ⬇️

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

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

一个例子
这样说,可能还是有点抽象,我把一段代码的时序图画出来,方便大家验证/思考。 我以 myAnnotation.value() 为例,用时序图来分析背后的逻辑(myAnnotation.value() 的位置如下图所示 ⬇️)

value() method in MyAnnotation ⬇️ participant h as h Note right of h: ⬆️ h is an AnnotationInvocationHandler instance participant mv as memberValues Note right of mv: memberValues is a map field within h m ->> p: value() p ->> h: invoke(this, m3, null) h ->> mv: get("value") mv -->> h: return "some arbitrary value" h -->> p: return "some arbitrary value" p -->> m: return "some arbitrary value"
注意
- 这张时序图中只涉及
main(...)方法里的myAnnotation.value() - 这张时序图中只画了各个方法的主要逻辑。参数检查,异常处理之类的逻辑都没画。
上方时序图的解释
m3是一个Method对象,它和MyAnnotation中的value()方法对应h的精确类型是AnnotationInvocationHandler,所以h里有memberValues字段$Proxy1implement了MyAnnotation接口,所以可以通过$Proxy1的实例来调用value()方法 (MyAnnotation中定义了value()方法),Intellij IDEA中所展示的$Proxy1.class中的value()方法的逻辑如下 ⬇️

小结
在 AnnotationInvocationHandler 的 invoke(Object proxy, Method method, Object[] args) 方法中,会
- 找到和
method参数对应的返回值- 如果
method和以下4个方法中的某一个对应,则进入相应的处理逻辑Object.equals(Object)Object.toString()Object.hashCode()Annotation.annotationType()
- 否则,用
this.memberValues查询和method对应的返回值
- 如果
任务列表更新后如下 ⬇️
-
AnnotationInvocationHandler的构造函数 -
AnnotationInvocationHandler的invoke(Object proxy, Method method, Object[] args)方法