zoukankan      html  css  js  c++  java
  • Java注解(Annotation)原理详解(转)

    add by zhj: 注解在Java内部其实就是一个接口,对于每个注解都会生成一个动态代理类,准确的说只有通过反射获取到注解时,Java才会生成这个动态代理类,对于每个使用的地方,会使用化该动态代理类。

    文中其实不仅提到了注解的实现方式,还提到了如何将动态代理类class输出到.class文件,利用Idea的自动反编译功能,就能看到Java源码,这个太有帮助了。看到原文的评论中,有人提到

    “memberValues这个Map对象是怎么生成的,继续调试通过方法调用栈找到memberValues的本源 ,这句话何解”,memberValues是AnnotationInvocationHandler的属性,是在调用AnnotationInvocationHandler的构造方法

    时以参数形式传入的,在这个构造方法中打断点就可以看到了,单独跟踪可以找到实例化AnnotationInvocationHandler的地方是在AnnotationParser类中,就看到memberValues是如何生成的了,亲测。

    原文:https://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());
            }
        }
    }

    从第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)

    看到第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;
                    }
                }
            }
        }
        .......
    }

    我们直接从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)

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

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

    3. 总结

    注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
    ————————————————
    版权声明:本文为CSDN博主「心中要有一片海」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/lylwo317/article/details/52163304

    
    
    
  • 相关阅读:
    python之map,filter
    python函数的闭包
    Hibernate查询对象的方法浅析
    底部浮动
    DataGrid-自定义排序
    DataGrid-1
    Alert
    2014-01-04 SQL练习
    proguard-gui 混淆代码简要笔记
    vim利用coc补全的配置过程
  • 原文地址:https://www.cnblogs.com/ajianbeyourself/p/14560581.html
Copyright © 2011-2022 走看看