from:http://blog.csdn.net/lylwo317/article/details/52163304
序言
注解在Java中到底是什么樣的東西?具體是如何實現的?
本文將一層一層深入探究注解的實現原理。為了盡可能的將分析的過程呈現出來,所以文章包含了大量的截圖和代碼。(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<? extends Annotation> 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 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
我們直接從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."<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
仔細看第40行#29 = Utf8 Do it!
,可以看到#29與var3的29對應(也就常量池的索引),對應的值就是Do it!
。
以上就是say方法調用的細節。
總結
注解本質是一個繼承了Annotation的特殊接口,其具體實現類是Java運行時生成的動態代理類。通過代理對象調用自定義注解(接口)的方法,會最終調用AnnotationInvocationHandler的invoke方法。該方法會從memberValues這個Map中索引出對應的值。而memberValues的來源是Java常量池。