• Java注解(Annotation)原理详解


    转载自:http://blog.csdn.net/lylwo317/article/details/52163304

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

    //先自定义一个运行时注解
    @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(say = "Do it!")
    public class TestMain {
        public static void main(String[] args) {
            /* 设置此系统属性,让JVM生成的Proxy类写入文件.保存路径为:com/sun/proxy(如果不存在请手工创建) */
            System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
            HelloAnnotation annotation = TestMain.class.getAnnotation(HelloAnnotation.class);//获取TestMain类上的注解对象
            System.out.println(annotation.say());//调用注解对象的say方法,并打印到控制台
        }
    }

    然后运行程序。
    这里写图片描述
      可以看到,已经导出了运行时生成的代理类,而且每个分别实现了一个接口。
    HelloAnnotation的动态代理类是$Proxy3.class,Intellij自带了反编译工具,直接双击打开,得到如下的Java代码:

    public final class $Proxy3 extends Proxy implements HelloAnnotation {
        private static Method m3;
        private static Method m1;
        private static Method m0;
        private static Method m4;
        private static Method m2;
    
        public $Proxy3(InvocationHandler var1) throws  {
            super(var1);
        }
    
        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 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 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);
            }
        }
    
        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 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);
            }
        }
    
        static {
            try {
                m3 = Class.forName("HelloAnnotation").getMethod("say", new Class[0]);
                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
                m4 = Class.forName("HelloAnnotation").getMethod("annotationType", new Class[0]);
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }

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

     $ javap -verbose HelloAnnotation 
     public interface HelloAnnotation extends java.lang.annotation.Annotation
      SourceFile: "HelloAnnotation.java"
      RuntimeVisibleAnnotations:
        0: #11(#12=[e#13.#14])
        1: #15(#12=e#16.#17)
      minor version: 0
      major version: 51
      flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
    Constant pool:
       #1 = Class              #18            //  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               HelloAnnotation
      #19 = Utf8               java/lang/Object
      #20 = Utf8               java/lang/annotation/Annotation
    {
      public abstract java.lang.String say();
        flags: ACC_PUBLIC, ACC_ABSTRACT
        AnnotationDefault:
          default_value: s#7}

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

    public interface Annotation {
        boolean equals(Object var1);
        int hashCode();
        String toString();
        Class<? extends Annotation> annotationType();
    }

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

    小结

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

    二、动态代理类$Proxy3如何处理annotation.say()方法的调用

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

    $Proxy3.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);
            }
        }
        ....
    }

      从上面不难看出,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()方法的调用的。
    这里写图片描述
      可以看到,say方法的返回值是从一个Map中获取到的。这个map以key(注解方法名)—value(注解方法对应的值)存储TestMain类上的注解

      那memberValues这个Map对象是怎么生成的,继续调试
      通过方法调用栈找到memberValues的本源。
    这里写图片描述
    ‘do,it’是在parseMemberValue()方法中获取的,我们继续跟进parseMemberValue()方法

    public static Object parseMemberValue(Class<?> var0, ByteBuffer var1, ConstantPool var2, Class<?> var3) {
            Object var4 = null;
            byte var5 = var1.get();
            switch(var5) {
            case 64:
                var4 = parseAnnotation(var1, var2, var3, true);
                break;
            case 91:
                return parseArray(var0, var1, var2, var3);
            case 99:
                var4 = parseClassValue(var1, var2, var3);
                break;
            case 101:
                return parseEnumValue(var0, var1, var2, var3);
            default:
                var4 = parseConst(var5, var1, var2); //此处会调用parseConst方法,继续跟进到parseConst方法
            }
    
            if(!(var4 instanceof ExceptionProxy) && !var0.isInstance(var4)) {
                var4 = new AnnotationTypeMismatchExceptionProxy(var4.getClass() + "[" + var4 + "]");
            }
            return var4;
        }
    
        private static Object parseConst(int var0, ByteBuffer var1, ConstantPool var2) {
            int var3 = var1.getShort() & 'uffff';
            switch(var0) {
            case 66:
                return Byte.valueOf((byte)var2.getIntAt(var3));
            case 67:
                return Character.valueOf((char)var2.getIntAt(var3));
            case 68:
                return Double.valueOf(var2.getDoubleAt(var3));
            case 70:
                return Float.valueOf(var2.getFloatAt(var3));
            case 73:
                return Integer.valueOf(var2.getIntAt(var3));
            case 74:
                return Long.valueOf(var2.getLongAt(var3));
            case 83:
                return Short.valueOf((short)var2.getIntAt(var3));
            case 90:
                return Boolean.valueOf(var2.getIntAt(var3) != 0);
            case 115:
                return var2.getUTF8At(var3); //memberValues是通过常量池获取到,这里的var3就是常量池中的序号。
            default:
                throw new AnnotationFormatError("Invalid member-value tag in annotation: " + var0);
            }
        }

      调用完parseConst方法,然后返回到parseMemberValue()方法
    这里写图片描述
      可以看到获取的就是我们定义在TestMain类上注解的say的值——“Do it!”

      这里可以通过javap -verbose TestMain查看TestMain字节码中的常量池。

    $ javap -verbose TestMain                                           
    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! //这里就是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

      到此为止,say方法就完成了。

    总结

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

  • 相关阅读:
    冲刺阶段第三天
    冲刺阶段第二天
    冲刺阶段第一天
    工作项估计
    学习进度条(7-9周)
    团队计划会议01
    第一次冲刺阶段(一)
    软件项目团队报告
    团队项目会议第一次
    团队开发项目需求简介
  • 原文地址:https://www.cnblogs.com/qingchen521/p/8575763.html
Copyright © 2020-2023  润新知