浅析 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.class
MyAnnotation.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
:0x2000
ACC_ABSTRACT
:0x0400
ACC_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.class
AnnotationStudy.class
MyAnnotation.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
字段$Proxy1
implement
了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)
方法