目录

注解底层原理

需要的知识储备

  1. 知道如何自定义注解
  2. 理解Java动态代理机制
  3. 了解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());
    }
}

运行程序,输出结果如下:

1
Do it!

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

注解对象具体是什么

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

http://img.cana.space/picStore/20201112094515.png

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

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

  1. 在代码中加入System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
  2. 在运行时加入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具体实现类是哪个。

http://img.cana.space/picStore/20201112100428.png

可以看到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()方法的调用的。

http://img.cana.space/picStore/20201112101319.png

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

那memberValues这个Map对象是怎么生成的,继续调试,通过方法调用栈找到memberValues的本源

http://img.cana.space/picStore/20201112104705.png

图中的1、2、3、4分别表示:

  1. 声明存储注解信息的Map变量

  2. var14 存放的key

    20201112105148

    可以看到key是从TestMain的运行时常量池中获取,符号索引是30

  3. parsetMemberValue获取属性值

    点进去查看

    http://img.cana.space/picStore/20201112105445.png

    可以看到value也是从TestMain的运行时常量池中获取,符号索引是31

  4. 填充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方法调用的细节。

总结

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