from:http://blog.csdn.net/lylwo317/article/details/52163304
注解在中到底是什么样的东西?具体是如何实现的?
本文将一层一层深入探究注解的实现原理。为了尽可能的将分析的过程呈现出来,所以文章包含了大量的截图和代码。(ps:如果图片看不清楚,请将网页放大来看,chrome可以通过ctrl 鼠标滚轮放大)
知识方面
开始分析前,提醒一下,下面的分析必须具备以下知识
1. 知道如何自定义注解
2. 理解java动态代理机制
3. 了解java常量池
如果不具备以上的知识,会看得云里雾里的。上面提到的知识点谷歌百度都可以找到许多相关的文章。
工具方面
- intellij 2016
首先写一个简单的自定义注解小程序。
先自定义一个运行时注解
@target(elementtype.type) @retention(retentionpolicy.runtime) public @interface helloannotation { string say() default "hi"; }
然后在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方法,并打印到控制台 } }
运行程序,输出结果如下:
do it!
下面将围绕上面的代码来研究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
从第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
看到第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 annotationtype(); }
这些方法,已经被$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); } } .... }
proxy.java
public class proxy implements java.io.serializable { /** * the invocation handler for this proxy instance. * @serial */ protected invocationhandler h;
从上面不难看出,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 classextends annotation> type; private final map<string, object> membervalues; private transient volatile method[] membermethods = null; annotationinvocationhandler(classextends 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
我们直接从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()); } }
可以看到,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."":()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 // "":()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."":()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
仔细看第40行#29 = utf8 do it!
,可以看到#29与var3的29对应(也就常量池的索引),对应的值就是do it!
。
以上就是say方法调用的细节。
注解本质是一个继承了annotation的特殊接口,其具体实现类是java运行时生成的动态代理类。通过代理对象调用自定义注解(接口)的方法,会最终调用annotationinvocationhandler的invoke方法。该方法会从membervalues这个map中索引出对应的值。而membervalues的来源是java常量池。