前提

用Java快三年了,注解算是一个常用的类型,特别是在一些框架里面会大量使用注解做组件标识、配置或者策略。但是一直没有深入去探究JDK中的注解到底是什么,底层是怎么实现了?于是参考了一些资料,做了一次稍微详细的分析。

JDK的注解描述

参考JavaSE-8里面的JLS-9.6对注解的描述如下:

JDK中注解的底层实现-LMLPHP

注解的声明如下:

{InterfaceModifier} @ interface Identifier AnnotationTypeBody

接口修饰符 @ interface 注解标识符 注解类型的内容

其中:

  • 注解类型声明中的标识符指定了注解类型的名称。
  • 如果注解类型与它的任何封闭类或接口具有相同的简单名称,则编译时会出现错误。
  • 每个注解类型的直接父接口都是java.lang.annotation.Annotation

既然所有注解类型的父接口都是java.lang.annotation.Annotation,那么我们可以看一下Annotation接口的文档:

JavaSE-8中的文档对Annotation的描述和JLS-9.6中差不多,不过最后指明了可重复注解的兼容性考虑的问题,可重复主机在JDK1.8中由元注解@Repeatable实现。下面基于JDK8的最后一个版本java version 1.8.0_181探究一下注解在JDK中的底层实现。

注解实现探究

我们先定义一个十分简单的Counter注解如下:

package club.throwable.annotation;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
public @interface Counter {

    int count() default 0;
}

我们先从直接使用@Counter注解,从直观上观察@Counter实例的类型:

@Counter(count = 1)
public class Main {

    public static void main(String[] args) throws Exception{
        Counter counter = Main.class.getAnnotation(Counter.class);
        System.out.println(counter.count());
    }
}

JDK中注解的底层实现-LMLPHP

@Counter实例从Debug过程中观察发现是JDK的一个代理类(并且InvocationHandler的实例是sun.reflect.annotation.AnnotationInvocationHandler,它是一个修饰符为default的sun包内可用的类),为了验证这一点我们使用JDK的反编译命令查看@Counter的字节码:

javap -c -v D:\Projects\rxjava-seed\target\classes\club\throwable\annotation\Counter.class

@Counter反编译后的字节码如下:

Classfile /D:/Projects/rxjava-seed/target/classes/club/throwable/annotation/Counter.class
  Last modified 2018-10-6; size 487 bytes
  MD5 checksum 83cee23f426e5b51a096281068d8b555
  Compiled from "Counter.java"
public interface club.throwable.annotation.Counter extends java.lang.annotation.Annotation
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
   #1 = Class              #19            // club/throwable/annotation/Counter
   #2 = Class              #20            // java/lang/Object
   #3 = Class              #21            // java/lang/annotation/Annotation
   #4 = Utf8               count
   #5 = Utf8               ()I
   #6 = Utf8               AnnotationDefault
   #7 = Integer            0
   #8 = Utf8               SourceFile
   #9 = Utf8               Counter.java
  #10 = Utf8               RuntimeVisibleAnnotations
  #11 = Utf8               Ljava/lang/annotation/Retention;
  #12 = Utf8               value
  #13 = Utf8               Ljava/lang/annotation/RetentionPolicy;
  #14 = Utf8               RUNTIME
  #15 = Utf8               Ljava/lang/annotation/Documented;
  #16 = Utf8               Ljava/lang/annotation/Target;
  #17 = Utf8               Ljava/lang/annotation/ElementType;
  #18 = Utf8               TYPE
  #19 = Utf8               club/throwable/annotation/Counter
  #20 = Utf8               java/lang/Object
  #21 = Utf8               java/lang/annotation/Annotation
{
  public abstract int count();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_ABSTRACT
    AnnotationDefault:
      default_value: I#7}
SourceFile: "Counter.java"
RuntimeVisibleAnnotations:
  0: #11(#12=e#13.#14)
  1: #15()
  2: #16(#12=[e#17.#18])

如果熟悉字节码,从直观上可以得到下面的信息:

  • 注解是一个接口,它继承自java.lang.annotation.Annotation父接口。
  • @Counter对应的接口接口除了继承了java.lang.annotation.Annotation中的抽象方法,自身定义了一个抽象方法public abstract int count();

既然注解最后转化为一个接口,注解中定义的注解成员属性会转化为抽象方法,那么最后这些注解成员属性怎么进行赋值的呢?答案就是:为注解对应的接口生成一个实现该接口的动态代理类。直接点说就是:Java通过动态代理的方式生成了一个实现了"注解对应接口"的实例,该代理类实例实现了"注解成员属性对应的方法",这个步骤类似于"注解成员属性"的赋值过程,这样子就可以在程序运行的时候通过反射获取到注解的成员属性(这里注解必须是运行时可见的,也就是使用了@Retention(RetentionPolicy.RUNTIME),另外需要理解JDK原生动态代理和反射相关内容)。

注解对应的动态代理类实例

上面一些已经指出了,注解的最底层实现就是一个JDK的动态代理类,而这个动态代理类的生成过程我们完全可以通过Debug跟踪,这里列举一下笔者跟踪整个过程的流水账:

  • 1、Class<?>#getAnnotation(Class<A> annotationClass),通过类型获取注解实例。
  • 2、Class<?>#annotationData(),获取注解的数据。
  • 3、Class<?>#createAnnotationData(int classRedefinedCount),构建注解的数据。
  • 4、AnnotationParser#parseAnnotations(byte[] var0, ConstantPool var1, Class<?> var2),这里已经是sun包下的类,无法看到源码,这个方法用于解析注解,这一步使用到字节码中常量池的索引解析,常量解析完毕会生成一个成员属性键值对作为下一个环节的入参,常量池的解析可以看AnnotationParser#parseMemberValue方法
  • 5、AnnotationParser#annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1),同样是sun.reflect.annotation.AnnotationParser中的方法,用于生成注解的动态代理类。

注意第5步,贴出它的源码:

    public static Annotation annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1) {
        return (Annotation)AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
            public Annotation run() {
                return (Annotation)Proxy.newProxyInstance(var0.getClassLoader(), new Class[]{var0}, new AnnotationInvocationHandler(var0, var1));
            }
        });
    }

属性JDK动态代理的这里的代码应该看起来很简单,就是生成一个标准的JDK动态代理,而InvocationHandler的实例是AnnotationInvocationHandler,可以看它的成员变量、构造方法和实现InvocationHandler接口的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 this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                //利用方法名称从memberValues获取成员属性的赋值
                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;
                }
            }
        }
    }
//忽略其他方法    

这里需要重点注意一下的是:AnnotationInvocationHandler的成员变量Map<String, Object> memberValues存放着注解的成员属性的名称和值的映射,注解成员属性的名称实际上就对应着接口中抽象方法的名称,例如上面我们定义的@Counter注解生成代理类后,它的AnnotationInvocationHandler实例中的memberValues属性存放着键值对count=1

既然知道了注解底层使用了JDK原生的Proxy,那么我们可以直接输出代理类到指定目录去分析代理类的源码,有两种方式可以输出Proxy类的源码:

  • 1、通过Java系统属性设置System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
  • 2、通过-D参数指定,其实跟1差不多,参数是:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

这里使用方式1,修改一下上面用到的main方法:

    public static void main(String[] args) throws Exception {
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        Counter counter = Main.class.getAnnotation(Counter.class);
        System.out.println(counter.count());
    }

执行完毕之后,项目中多了一个目录:

JDK中注解的底层实现-LMLPHP

其中$Proxy0是@Retention注解对应的动态代理类,而$Proxy1才是我们的@Counter对应的动态代理类,当然如果有更多的注解,那么有可能生成$ProxyN。接着我们直接看$Proxy1的源码:

public final class $Proxy1 extends Proxy implements Counter {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    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 int count() throws  {
        try {
            return (Integer)super.h.invoke(this, m3, (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 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");
            m3 = Class.forName("club.throwable.annotation.Counter").getMethod("count");
            m4 = Class.forName("club.throwable.annotation.Counter").getMethod("annotationType");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

显然,$Proxy实现了Counter接口,它在代码的最后部分使用了静态代码块实例化了成员方法的Method实例,在前面的代码对这些Method进行了缓存,在调用成员方法的时候都是直接委托到InvocationHandler(AnnotationInvocationHandler)实例完成调用。我们在分析AnnotationInvocationHandler的时候看到,它只用到了Method的名称从Map从匹配出成员方法的结果,因此调用过程并不是反射调用,反而是直接的调用,效率类似于通过Key从Map实例中获取Value一样,是十分高效的。

小结

既然知道了注解的底层原理,我们可以编写一个"注解接口"和InvocationHandler实现来简单模拟整个过程。先定义一个接口:

public interface CounterAnnotation extends Annotation {

    int count();
}

InvocationHandler的简单实现:

public class CounterAnnotationInvocationHandler implements InvocationHandler {

    private final Map<String, Object> memberValues;
    private final Class<? extends Annotation> clazz;

    public CounterAnnotationInvocationHandler(Map<String, Object> memberValues, Class<? extends Annotation> clazz) {
        this.memberValues = memberValues;
        this.clazz = clazz;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Object value;
        switch (methodName) {
            case "toString":
                value = super.toString();
                break;
            case "hashCode":
                value = super.hashCode();
                break;
            case "equals":
                value = super.equals(args[0]);
                break;
            case "annotationType":
                value = clazz;
                break;
            default:
                value = memberValues.get(methodName);
        }
        return value;
    }
}

编写一个main方法:

public class CounterAnnotationMain {

    public static void main(String[] args) throws Exception{
        //这里模拟了注解成员属性从常量池解析的过程
        Map<String,Object> values = new HashMap<>(8);
        values.put("count", 1);
        //生成代理类
        CounterAnnotation proxy = (CounterAnnotation)Proxy.newProxyInstance(CounterAnnotationMain.class.getClassLoader(),
                new Class[]{CounterAnnotation.class},
                new CounterAnnotationInvocationHandler(values, CounterAnnotation.class));
        System.out.println(proxy.count());
    }
}
//运行后控制台输出:1

(本文完 c-1-d e-20181006)

10-06 16:16