Java之道系列:Annotation实现浅析

来源:互联网 发布:淘宝双11红包口令 编辑:程序博客网 时间:2024/05/17 07:40

今天来研究研究Java的注解是如何实现的。最简单的,先来看看注解的class文件是什么样的。

class文件支持

直接看栗子,

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Ann0tation {    String attr();}

javap一下,

Classfile /home/blues/Projects/just4fun/out/production/just4fun/me/kisimple/just4fun/Ann0tation.class  Last modified Feb 10, 2015; size 432 bytes  MD5 checksum d87c1a02d469944f093b8a1815add76f  Compiled from "Ann0tation.java"public interface me.kisimple.just4fun.Ann0tation extends java.lang.annotation.Annotation  SourceFile: "Ann0tation.java"  RuntimeVisibleAnnotations:    0: #9(#10:[e#11.#12])    1: #13(#10:e#14.#15)  minor version: 0  major version: 51  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATIONConstant pool:   #1 = Class              #16;           // me/kisimple/just4fun/Ann0tation   #2 = Class              #17;           //  java/lang/Object   #3 = Class              #18;           //  java/lang/annotation/Annotation   #4 = Utf8               attr;   #5 = Utf8               ()Ljava/lang/String;;   #6 = Utf8               SourceFile;   #7 = Utf8               Ann0tation.java;   #8 = Utf8               RuntimeVisibleAnnotations;   #9 = Utf8               Ljava/lang/annotation/Target;;  #10 = Utf8               value;  #11 = Utf8               Ljava/lang/annotation/ElementType;;  #12 = Utf8               METHOD;  #13 = Utf8               Ljava/lang/annotation/Retention;;  #14 = Utf8               Ljava/lang/annotation/RetentionPolicy;;  #15 = Utf8               RUNTIME;  #16 = Utf8               me/kisimple/just4fun/Ann0tation;  #17 = Utf8               java/lang/Object;  #18 = Utf8               java/lang/annotation/Annotation;{  public abstract java.lang.String attr();    flags: ACC_PUBLIC, ACC_ABSTRACT}

可以看到,所有的注解都继承了java.lang.annotation.Annotation这个接口,

public interface me.kisimple.just4fun.Ann0tation extends java.lang.annotation.Annotation

接下来看下使用了注解后的class文件,

public class Main {    @Ann0tation(attr = "hello main.")    public static void main(String[] args) throws Exception {        Method mm = Main.class.getMethod("main", String[].class);        for (Annotation anno : mm.getDeclaredAnnotations()) {            if(anno instanceof Ann0tation) {                System.out.println(((Ann0tation)anno).attr());            }        }    }}
  public static void main(java.lang.String[]) throws java.lang.Exception;    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=6, locals=6, args_size=1         0: ldc_w         #2;                 // class me/kisimple/just4fun/Main         3: ldc           #3;                 // String main         5: iconst_1               6: anewarray     #4;                 // class java/lang/Class         9: dup                   10: iconst_0              11: ldc_w         #5;                 // class "[Ljava/lang/String;"        14: aastore               15: invokevirtual #6;                 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;        18: astore_1              19: aload_1               20: invokevirtual #7;                 // Method java/lang/reflect/Method.getDeclaredAnnotations:()[Ljava/lang/annotation/Annotation;        23: astore_2              24: aload_2               25: arraylength           26: istore_3              27: iconst_0              28: istore        4        30: iload         4        32: iload_3               33: if_icmpge     72        36: aload_2               37: iload         4        39: aaload                40: astore        5        42: aload         5        44: instanceof    #8;                 // class me/kisimple/just4fun/Ann0tation        47: ifeq          66        50: getstatic     #9;                 // Field java/lang/System.out:Ljava/io/PrintStream;        53: aload         5        55: checkcast     #8;                 // class me/kisimple/just4fun/Ann0tation        58: invokeinterface #10,  1;          // InterfaceMethod me/kisimple/just4fun/Ann0tation.attr:()Ljava/lang/String;        63: invokevirtual #11;                // Method java/io/PrintStream.println:(Ljava/lang/String;)V        66: iinc          4, 1        69: goto          30        72: return              LineNumberTable:        line 13: 0        line 14: 19        line 15: 42        line 16: 50        line 14: 66        line 19: 72      LocalVariableTable:        Start  Length  Slot  Name   Signature              42      24     5  anno   Ljava/lang/annotation/Annotation;              24      48     2  arr$   [Ljava/lang/annotation/Annotation;              27      45     3  len$   I              30      42     4    i$   I               0      73     0  args   [Ljava/lang/String;              19      54     1    mm   Ljava/lang/reflect/Method;      StackMapTable: number_of_entries = 3           frame_type = 255 /* full_frame */          offset_delta = 30          locals = [ class "[Ljava/lang/String;", class java/lang/reflect/Method, class "[Ljava/lang/annotation/Annotation;", int, int ]          stack = []           frame_type = 35 /* same */           frame_type = 248 /* chop */          offset_delta = 5    Exceptions:      throws java.lang.Exception    RuntimeVisibleAnnotations:      0: #39(#40:s#41)

会发现最后多出来了RuntimeVisibleAnnotations这么一个属性,而它的值就是我们给main方法添加的注解,

  #39 = Utf8               Lme/kisimple/just4fun/Ann0tation;;  #40 = Utf8               attr;  #41 = Utf8               hello main.;

其实在上面Ann0tation的class文件里面就有这个属性了,因为Ann0tation使用了Target跟Retention注解。关于该属性可以参考虚拟机规范。
这么看下来,注解的定义与普通的接口其实并没有什么两样,只是编译器会在使用了注解的地方去添加一个RuntimeVisibleAnnotations属性,保存下我们添加的注解。这样即使在运行时,我们还是可以通过class文件来拿到我们添加的注解。

反射注解实例

既然注解只是个普通的接口,那么当我们使用反射去拿到的注解的实例(看上面的栗子)又是个什么鬼?看看源码就知道了。
上面我们所调用的Method#getDeclaredAnnotations,最后是会去调用
sun.reflect.annotation.AnnotationParser#parseAnnotations

    /**     * Parses the annotations described by the specified byte array.     * resolving constant references in the specified constant pool.     * The array must contain an array of annotations as described     * in the RuntimeVisibleAnnotations_attribute:     *     *   u2 num_annotations;     *   annotation annotations[num_annotations];     *     * @throws AnnotationFormatError if an annotation is found to be     *         malformed.     */    public static Map<Class<? extends Annotation>, Annotation> parseAnnotations(                byte[] rawAnnotations,                ConstantPool constPool,                Class<?> container) {        if (rawAnnotations == null)            return Collections.emptyMap();        try {            return parseAnnotations2(rawAnnotations, constPool, container, null);        } catch(BufferUnderflowException e) {            throw new AnnotationFormatError("Unexpected end of annotations.");        } catch(IllegalArgumentException e) {            // Type mismatch in constant pool            throw new AnnotationFormatError(e);        }    }

注解的解析其实就是去处理上面我们所说到的class文件中的RuntimeVisibleAnnotations属性相关的一些东东。继续往下看,最后会来到annotationForMap方法,

    public static Annotation annotationForMap(        Class<? extends Annotation> type, Map<String, Object> memberValues)    {        return (Annotation) Proxy.newProxyInstance(            type.getClassLoader(), new Class[] { type },            new AnnotationInvocationHandler(type, memberValues));    }

原来是使用动态代理实例化了一个注解的接口返回,那我们可以使用SA中的ClassDump把这个动态代理类dump下来,再用jd反编译看看,结果如下,

package com.sun.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;import me.kisimple.just4fun.Ann0tation;public final class $Proxy1 extends Proxy  implements Ann0tation{  private static Method m1;  private static Method m3;  private static Method m0;  private static Method m4;  private static Method m2;  public $Proxy1(InvocationHandler paramInvocationHandler)  {    super(paramInvocationHandler);  }  static  {    try    {      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });      m3 = Class.forName("me.kisimple.just4fun.Ann0tation").getMethod("attr", new Class[0]);      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);      m4 = Class.forName("me.kisimple.just4fun.Ann0tation").getMethod("annotationType", new Class[0]);      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);      return;    }    catch (NoSuchMethodException localNoSuchMethodException)    {      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());    }    catch (ClassNotFoundException localClassNotFoundException)    {      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());    }  }  public final boolean equals(Object paramObject)  {    try    {      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }  public final String toString()  {    try    {      return (String)this.h.invoke(this, m2, null);    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }  public final int hashCode()  {    try    {      return ((Integer)this.h.invoke(this, m0, null)).intValue();    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }  public final Class annotationType()  {    try    {      return (Class)this.h.invoke(this, m4, null);    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }  public final String attr()  {    try    {      return (String)this.h.invoke(this, m3, null);    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }}

而AnnotationInvocationHandler做的事情很简单,举上面的栗子,当我们调用((Ann0tation)anno).attr()时,AnnotationInvocationHandler直接从memberValues这个map取出来并返回,而这个map就是通过解析RuntimeVisibleAnnotations得到并传给AnnotationInvocationHandler的。
所以就像R大说的,注解就是接口+Map,然后通过动态代理将他们组合起来就OK了^_^

参考资料

  • http://rednaxelafx.iteye.com/blog/1148983
0 0