您的位置:首页 > 编程语言 > Java开发

Java注解(Annotation)原理详解

2017-04-12 21:08 726 查看

序言

注解在Java中到底是什么样的东西?具体是如何实现的?

本文将一层一层深入探究注解的实现原理。为了尽可能的将分析的过程呈现出来,所以文章包含了大量的截图和代码。(ps:如果图片看不清楚,请将网页放大来看,chrome可以通过ctrl+鼠标滚轮放大)

前期准备

知识方面

开始分析前,提醒一下,下面的分析必须具备以下知识

1. 知道如何自定义注解

2. 理解Java动态代理机制

3. 了解Java常量池

如果不具备以上的知识,会看得云里雾里的。上面提到的知识点谷歌百度都可以找到许多相关的文章。

工具方面

Intellij 2016

开始分析

首先写一个简单的自定义注解小程序。

先自定义一个运行时注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloAnnotation {

String say() default "Hi";

}
1
2
3
4
5
6
7
1
2
3
4
5
6
7
[/code]

然后在Main函数中解析注解
@HelloAnnotation(say = "Do it!")
public class TestMain {
public static void main(String[] args) {
HelloAnnotation annotation = TestMain.class.getAnnotation(HelloAnnotation.class);//获取TestMain类上的注解对象
System.out.println(annotation.say());//调用注解对象的say方法,并打印到控制台
}
}
1
2
3
4
5
6
7
1
2
3
4
5
6
7
[/code]

运行程序,输出结果如下:
Do it!
1
1
[/code]

下面将围绕上面的代码来研究Java注解(Annotation)的实现原理

1. 注解对象具体是什么?

首先,我们先在main函数第一行断点,看看HelloAnnotation具体是什么类的对象



可以看到HelloAnnotation注解的实例是jvm生成的动态代理类的对象。

这个运行时生成的动态代理对象是可以导出到文件的,方法有两种

在代码中加入
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

在运行时加入jvm 参数
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true


这里使用第一种,↓



然后运行程序。



可以看到,已经导出了运行时生成的代理类。↑

HelloAnnotation的动态代理类是$Proxy1.class,Intellij自带了反编译工具,直接双击打开,得到如下的Java代码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import com.kevin.java.annotation.runtimeAnnotation.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})).booleanValue();
} 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)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m4 = Class.forName("com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation").getMethod("annotationType", new Class[0]);
m3 = Class.forName("com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation").getMethod("say", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
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
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
[/code]

从第14行我们可以看到,我们自定义的注解HelloAnnotation是一个接口,而$Proxy1这个Java生成的动态代理类就是它的实现类

我们接着看一下HelloAnnotation的字节码
$ javap -verbose HelloAnnotation
Warning: Binary file HelloAnnotation contains com.kevin.java.annotation.runtimeAnnotation.HelloAnnotation
Classfile /home/kevin/Workspace/IdeaProjects/JavaLearn/out/production/JavaLearn/com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation.class
Last modified Aug 6, 2016; size 496 bytes
MD5 checksum a6c87f863669f6ab9050ffa310160ea5
Compiled from "HelloAnnotation.java"
public interface com.kevin.java.annotation.runtimeAnnotation.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/kevin/java/annotation/runtimeAnnotation/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/kevin/java/annotation/runtimeAnnotation/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)
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
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
[/code]

看到第7行。很明显,HelloAnnotation就是继承了Annotation的接口。再看第10行,flag字段中,我们可以看到,有个
ACC_ANNOTATION
标记,说明是一个注解,所以注解本质是一个继承了Annotation的特殊接口。

而Annotation接口声明了以下方法。
package java.lang.annotation;

public interface Annotation {
boolean equals(Object var1);

int hashCode();

String toString();

Class<? extends Annotation> annotationType();
}
1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
[/code]

这些方法,已经被$Proxy1实现了。(这就是动态代理的机制)

小结

现在我们知道了HelloAnnotation注解(接口)是一个继承了Annotation接口的特殊接口,而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1,该类就是HelloAnnotation注解(接口)的具体实现类。


2. 动态代理类$Proxy1是如何处理annotation.say()方法的调用?

无论是否了解动态代理,这里只需要明确一点,动态代理方法的调用最终会传递给绑定的InvocationHandler实例的invoke方法处理。我们可以看看源码

$Proxy1.java
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);
}
}
....
}
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
[/code]

Proxy.java
public class Proxy implements java.io.Serializable {

/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
1
2
3
4
5
6
7
1
2
3
4
5
6
7
[/code]

从上面不难看出,say方法最终会执行
(String)super.h.invoke(this, m3, (Object[])null);
,而这其中的h对象类型就是InvocationHandler接口的某个实现类

断点调试,看看InvocationHandler具体实现类是哪个。



可以看到h对象是
AnnotationInvocationHandler
的实例。让我们来看看该实现类的invoke方法。
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 Integer.valueOf(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;
}
}
}
}
.......
}
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
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
[/code]

我们直接从invoke方法第一行开始单步调试,看看invoke方法是如何处理我们
annotation.say()
方法的调用的。

这里再贴一次测试代码,不然就得翻到前面了
@HelloAnnotation(say = "Do it!")
public class TestMain {
public static void main(String[] args) {
HelloAnnotation annotation = TestMain.class.getAnnotation(HelloAnnotation.class);
System.out.println(annotation.say());
}
}
1
2
3
4
5
6
7
1
2
3
4
5
6
7
[/code]



可以看到,say方法的返回值是从一个Map中获取到的。这个map以key(注解方法名)—value(注解方法对应的值)存储TestMain类上的注解

那memberValues这个Map对象是怎么生成的,继续调试

通过方法调用栈找到memberValues的本源



我们继续跟进parseMemberValue()方法



在parseMemberValue()中会调用parseConst方法,继续跟进到parseConst方法



可以看到,memberValues是通过常量池获取到,
return var2.getUTF8At(var3);
中的var3就是常量池中的序号。继续执行返回到parseMemberValue()方法



可以看到获取的就是我们定义在TestMain类上注解的say的值——“Do it!”

这里可以通过
javap -verbose TestMain
查看TestMain字节码中的常量池
$ javap -verbose TestMain
Warning: Binary file TestMain contains com.kevin.java.annotation.runtimeAnnotation.TestMain
Classfile /home/kevin/Workspace/IdeaProjects/JavaLearn/out/production/JavaLearn/com/kevin/java/annotation/runtimeAnnotation/TestMain.class
Last modified Aug 10, 2016; size 1117 bytes
MD5 checksum 610b7176c7dfdad08bc4862247df7123
Compiled from "TestMain.java"
public class com.kevin.java.annotation.runtimeAnnotation.TestMain
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool://常量池
#1 = Methodref          #11.#30        // java/lang/Object."<init>":()V
#2 = String             #31            // sun.misc.ProxyGenerator.saveGeneratedFiles
#3 = String             #32            // true
#4 = Methodref          #33.#34        // java/lang/System.setProperty:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#5 = Class              #35            // com/kevin/java/annotation/runtimeAnnotation/TestMain
#6 = Class              #36            // com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation
#7 = Methodref          #37.#38        // java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
#8 = Fieldref           #33.#39        // java/lang/System.out:Ljava/io/PrintStream;
#9 = InterfaceMethodref #6.#40         // com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation.say:()Ljava/lang/String;
#10 = Methodref          #41.#42        // java/io/PrintStream.println:(Ljava/lang/String;)V
#11 = Class              #43            // java/lang/Object
#12 = Utf8               <init>
#13 = Utf8               ()V
#14 = Utf8               Code
#15 = Utf8               LineNumberTable
#16 = Utf8               LocalVariableTable
#17 = Utf8               this
#18 = Utf8               Lcom/kevin/java/annotation/runtimeAnnotation/TestMain;
#19 = Utf8               main
#20 = Utf8               ([Ljava/lang/String;)V
#21 = Utf8               args
#22 = Utf8               [Ljava/lang/String;
#23 = Utf8               annotation
#24 = Utf8               Lcom/kevin/java/annotation/runtimeAnnotation/HelloAnnotation;
#25 = Utf8               SourceFile
#26 = Utf8               TestMain.java
#27 = Utf8               RuntimeVisibleAnnotations
#28 = Utf8               say
#29 = Utf8               Do it!
#30 = NameAndType        #12:#13        // "<init>":()V
#31 = Utf8               sun.misc.ProxyGenerator.saveGeneratedFiles
#32 = Utf8               true
#33 = Class              #44            // java/lang/System
#34 = NameAndType        #45:#46        // setProperty:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#35 = Utf8               com/kevin/java/annotation/runtimeAnnotation/TestMain
#36 = Utf8               com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation
#37 = Class              #47            // java/lang/Class
#38 = NameAndType        #48:#49        // getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
#39 = NameAndType        #50:#51        // out:Ljava/io/PrintStream;
#40 = NameAndType        #28:#52        // say:()Ljava/lang/String;
#41 = Class              #53            // java/io/PrintStream
#42 = NameAndType        #54:#55        // println:(Ljava/lang/String;)V
#43 = Utf8               java/lang/Object
#44 = Utf8               java/lang/System
#45 = Utf8               setProperty
#46 = Utf8               (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#47 = Utf8               java/lang/Class
#48 = Utf8               getAnnotation
#49 = Utf8               (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
#50 = Utf8               out
#51 = Utf8               Ljava/io/PrintStream;
#52 = Utf8               ()Ljava/lang/String;
#53 = Utf8               java/io/PrintStream
#54 = Utf8               println
#55 = Utf8               (Ljava/lang/String;)V
{
public com.kevin.java.annotation.runtimeAnnotation.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 10: 0
LocalVariableTable:
Start  Length  Slot  Name   Signature
0       5     0  this   Lcom/kevin/java/annotation/runtimeAnnotation/TestMain;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, 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/kevin/java/annotation/runtimeAnnotation/TestMain
10: ldc           #6                  // class com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation
12: invokevirtual #7                  // Method java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
15: checkcast     #6                  // class com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation
18: astore_1
19: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
22: aload_1
23: invokeinterface #9,  1            // InterfaceMethod com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation.say:()Ljava/lang/String;
28: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
31: return
LineNumberTable:
line 13: 0
line 14: 8
line 15: 19
line 16: 31
LocalVariableTable:
Start  Length  Slot  Name   Signature
0      32     0  args   [Ljava/lang/String;
19      13     1 annotation   Lcom/kevin/java/annotation/runtimeAnnotation/HelloAnnotation;
}
SourceFile: "TestMain.java"
RuntimeVisibleAnnotations:
0: #24(#28=s#29)
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
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
[/code]

仔细看第40行
#29 = Utf8 Do it!
,可以看到#29与var3的29对应(也就常量池的索引),对应的值就是
Do
it!


以上就是say方法调用的细节。

总结

注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。

原文地址:http://blog.csdn.net/lylwo317/article/details/52163304
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: