需要的知识储备
- 知道如何自定义注解
- 理解Java动态代理机制
- 了解Java常量池
开始分析
首先写一个简单的自定义注解小程序
先定义一个运行时注解
1
2
3
4
5
|
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloAnnotation {
String say() default "Hi";
}
|
然后在Main函数中解析注解
1
2
3
4
5
6
7
8
9
|
@HelloAnnotation(say = "Do it!")
public class TestMain {
public static void main(String[] args) {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
HelloAnnotation annotation = TestMain.class.getAnnotation(HelloAnnotation.class);
System.out.println(annotation.say());
}
}
|
运行程序,输出结果如下:
下面将围绕上面的代码来研究Java注解(Annotation)的实现原理
注解对象具体是什么
首先,我们先在main函数第一行断点,看看HelloAnnotation具体是什么类的对象

可以看到HelloAnnotation注解的实例是jvm生成的动态代理类($Proxy1)的对象。
这个运行时生成的动态代理对象是可以导出到文件的,方法有两种
- 在代码中加入
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
- 在运行时加入jvm 参数
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
这里使用第一种,然后运行程序,会在工程目录下生成com.sun.proxy包,里面有生成的代理类$Proxy1 ↓
HelloAnnotation的动态代理类是$Proxy1.class,Intellij自带了反编译工具,直接双击打开,得到如下的Java代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.sun.proxy;
import com.eh.cloud2020.config.HelloAnnotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy1 extends Proxy implements HelloAnnotation {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m3;
private static Method m0;
public $Proxy1(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Class annotationType() throws {
try {
return (Class)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String say() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.eh.cloud2020.config.HelloAnnotation").getMethod("annotationType");
m3 = Class.forName("com.eh.cloud2020.config.HelloAnnotation").getMethod("say");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
|
从第14行我们可以看到,我们自定义的注解HelloAnnotation是一个接口,而$Proxy1这个Java生成的动态代理类就是它的实现类
我们接着看一下HelloAnnotation的字节码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
$ javap -v target.classes.com.eh.cloud2020.config.HelloAnnotation
警告: 二进制文件target.classes.com.eh.cloud2020.config.HelloAnnotation包含com.eh.cloud2020.config.HelloAnnotation
Classfile /Users/david/my/study/project/cloud2020/cloud-config-center3344/target/classes/com/eh/cloud2020/config/HelloAnnotation.class
Last modified 2020-11-12; size 476 bytes
MD5 checksum 431401e4873f0f15d0c034bcc4a891b4
Compiled from "HelloAnnotation.java"
public interface com.eh.cloud2020.config.HelloAnnotation extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Class #18 // com/eh/cloud2020/config/HelloAnnotation
#2 = Class #19 // java/lang/Object
#3 = Class #20 // java/lang/annotation/Annotation
#4 = Utf8 say
#5 = Utf8 ()Ljava/lang/String;
#6 = Utf8 AnnotationDefault
#7 = Utf8 Hi
#8 = Utf8 SourceFile
#9 = Utf8 HelloAnnotation.java
#10 = Utf8 RuntimeVisibleAnnotations
#11 = Utf8 Ljava/lang/annotation/Target;
#12 = Utf8 value
#13 = Utf8 Ljava/lang/annotation/ElementType;
#14 = Utf8 TYPE
#15 = Utf8 Ljava/lang/annotation/Retention;
#16 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#17 = Utf8 RUNTIME
#18 = Utf8 com/eh/cloud2020/config/HelloAnnotation
#19 = Utf8 java/lang/Object
#20 = Utf8 java/lang/annotation/Annotation
{
public abstract java.lang.String say();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: s#7}
SourceFile: "HelloAnnotation.java"
RuntimeVisibleAnnotations:
0: #11(#12=[e#13.#14])
1: #15(#12=e#16.#17)
|
看到第7行。很明显,HelloAnnotation就是继承了Annotation的接口。再看第10行,flag字段中,我们可以看到,有个ACC_ANNOTATION
标记,说明是一个注解,所以注解本质是一个继承了Annotation的特殊接口。
而Annotation接口声明了以下方法。
1
2
3
4
5
6
7
8
9
10
11
|
package java.lang.annotation;
public interface Annotation {
boolean equals(Object var1);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
|
这些方法,已经被$Proxy1实现了。(这就是动态代理的机制)
小结
现在我们知道了HelloAnnotation注解(接口)是一个继承了Annotation接口的特殊接口,而我们通过反射获取注解时,返回的是Java运行时生成的动态代理$Proxy1,该类就是HelloAnnotation注解(接口)的具体实现类。
动态代理类$Proxy1是如何处理annotation.say()方法的调用
无论是否了解动态代理,这里只需要明确一点,动态代理方法的调用最终会传递给绑定的InvocationHandler实例的invoke方法处理。我们可以看看源码
$Proxy1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public final class $Proxy1 extends Proxy implements HelloAnnotation {
.....
public final String say() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
....
}
|
从上面不难看出,say方法最终会执行(String)super.h.invoke(this, m3, (Object[])null);
,而这其中的h对象类型就是InvocationHandler接口的某个实现类
断点调试,看看InvocationHandler具体实现类是哪个。

可以看到h对象是AnnotationInvocationHandler
的实例。让我们来看看该实现类的invoke方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
...
}
|
我们直接从invoke方法第一行开始单步调试,看看invoke方法是如何处理我们annotation.say()
方法的调用的。

可以看到,say方法的返回值是从一个Map中获取到的。这个map以key(注解方法名)—value(注解方法对应的值)存储TestMain类上的注解
那memberValues这个Map对象是怎么生成的,继续调试,通过方法调用栈找到memberValues的本源

图中的1、2、3、4分别表示:
-
声明存储注解信息的Map变量
-
var14 存放的key

可以看到key是从TestMain的运行时常量池中获取,符号索引是30
-
parsetMemberValue获取属性值
点进去查看

可以看到value也是从TestMain的运行时常量池中获取,符号索引是31
-
填充map
通过上面调试可以看到AnnotationInvocationHandler的成员变量memberValues的key和value都是从TestMain的运行时常量池中获取到的,索引分别是30和31,这里我们通过javap -v TestMain
查看TestMain字节码中的常量池,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
$ javap -v target.classes.com.eh.cloud2020.config.TestMain
警告: 二进制文件target.classes.com.eh.cloud2020.config.TestMain包含com.eh.cloud2020.config.TestMain
Classfile /Users/david/my/study/project/cloud2020/cloud-config-center3344/target/classes/com/eh/cloud2020/config/TestMain.class
Last modified 2020-11-12; size 1083 bytes
MD5 checksum d124b0acd3c3fe8c4489c66ad87f80c3
Compiled from "TestMain.java"
public class com.eh.cloud2020.config.TestMain
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #11.#32 // java/lang/Object."<init>":()V
#2 = String #33 // sun.misc.ProxyGenerator.saveGeneratedFiles
#3 = String #34 // true
#4 = Methodref #35.#36 // java/lang/System.setProperty:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#5 = Class #37 // com/eh/cloud2020/config/TestMain
#6 = Class #38 // com/eh/cloud2020/config/HelloAnnotation
#7 = Methodref #39.#40 // java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
#8 = InterfaceMethodref #6.#41 // com/eh/cloud2020/config/HelloAnnotation.say:()Ljava/lang/String;
#9 = Fieldref #35.#42 // java/lang/System.out:Ljava/io/PrintStream;
#10 = Methodref #43.#44 // java/io/PrintStream.println:(Ljava/lang/String;)V
#11 = Class #45 // java/lang/Object
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lcom/eh/cloud2020/config/TestMain;
#19 = Utf8 main
#20 = Utf8 ([Ljava/lang/String;)V
#21 = Utf8 args
#22 = Utf8 [Ljava/lang/String;
#23 = Utf8 annotation
#24 = Utf8 Lcom/eh/cloud2020/config/HelloAnnotation;
#25 = Utf8 result
#26 = Utf8 Ljava/lang/String;
#27 = Utf8 SourceFile
#28 = Utf8 TestMain.java
#29 = Utf8 RuntimeVisibleAnnotations
#30 = Utf8 say
#31 = Utf8 Do it!
#32 = NameAndType #12:#13 // "<init>":()V
#33 = Utf8 sun.misc.ProxyGenerator.saveGeneratedFiles
#34 = Utf8 true
#35 = Class #46 // java/lang/System
#36 = NameAndType #47:#48 // setProperty:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#37 = Utf8 com/eh/cloud2020/config/TestMain
#38 = Utf8 com/eh/cloud2020/config/HelloAnnotation
#39 = Class #49 // java/lang/Class
#40 = NameAndType #50:#51 // getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
#41 = NameAndType #30:#52 // say:()Ljava/lang/String;
#42 = NameAndType #53:#54 // out:Ljava/io/PrintStream;
#43 = Class #55 // java/io/PrintStream
#44 = NameAndType #56:#57 // println:(Ljava/lang/String;)V
#45 = Utf8 java/lang/Object
#46 = Utf8 java/lang/System
#47 = Utf8 setProperty
#48 = Utf8 (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#49 = Utf8 java/lang/Class
#50 = Utf8 getAnnotation
#51 = Utf8 (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
#52 = Utf8 ()Ljava/lang/String;
#53 = Utf8 out
#54 = Utf8 Ljava/io/PrintStream;
#55 = Utf8 java/io/PrintStream
#56 = Utf8 println
#57 = Utf8 (Ljava/lang/String;)V
{
public com.eh.cloud2020.config.TestMain();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/eh/cloud2020/config/TestMain;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: ldc #2 // String sun.misc.ProxyGenerator.saveGeneratedFiles
2: ldc #3 // String true
4: invokestatic #4 // Method java/lang/System.setProperty:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
7: pop
8: ldc #5 // class com/eh/cloud2020/config/TestMain
10: ldc #6 // class com/eh/cloud2020/config/HelloAnnotation
12: invokevirtual #7 // Method java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
15: checkcast #6 // class com/eh/cloud2020/config/HelloAnnotation
18: astore_1
19: aload_1
20: invokeinterface #8, 1 // InterfaceMethod com/eh/cloud2020/config/HelloAnnotation.say:()Ljava/lang/String;
25: astore_2
26: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
29: aload_2
30: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: return
LineNumberTable:
line 7: 0
line 8: 8
line 9: 19
line 10: 26
line 12: 33
LocalVariableTable:
Start Length Slot Name Signature
0 34 0 args [Ljava/lang/String;
19 15 1 annotation Lcom/eh/cloud2020/config/HelloAnnotation;
26 8 2 result Ljava/lang/String;
}
SourceFile: "TestMain.java"
RuntimeVisibleAnnotations:
0: #24(#30=s#31)
|
可以看到30和31分别对应着key——“say”和value——“Do it!”。
btw,查看main方法调用字节码中的程序执行位置(也就是之后存放到程序计数器的值)20,invokeinterface #8
也就是invokeinterface com/eh/cloud2020/config/HelloAnnotation.say:()Ljava/lang/String;
可以看到say方法调用使用的指令是invokeinterface
,在jvm中方法调用的指令有如下几种
- invokestatic 调用静态方法
- invokespecial 调用实例构造器方法,私有方法和指定的父类方法
- invokevirtual 调用所有的虚方法
- invokeinterface 调用接口方法,会在运行时确定一个实现该接口的对象
从这个角度也可以看出java编译器认为HelloAnnotation.say()是一个接口方法
以上就是say方法调用的细节。
总结
- 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。
- 该方法会从AnnotationInvocationHandler的成员变量memberValues这个Map中索引出对应的值。而memberValues的来源是标注该注解的类的Java常量池。