浅析java注解底层原理

964 阅读5分钟

注解是一种附加信息/标识信息,如果没有对应的处理器,那没有任何作用.

注解概述

元注解

@Retention

注解留存的时间节点:

  • RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件,常见的注解为@Override, @SuppressWarnings
  • RetentionPolicy.CLASS:会写入 class 文件,在类加载后丢弃,可以定义对字节码的操作
  • RetentionPolicy.RUNTIME:永久保存,可以反射获取

@Target

注解作用的范围:

  • ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
  • ElementType.FIELD:允许作用在属性字段上
  • ElementType.METHOD:允许作用在方法上
  • ElementType.PARAMETER:允许作用在方法参数上
  • ElementType.CONSTRUCTOR:允许作用在构造器上
  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
  • ElementType.ANNOTATION_TYPE:允许作用在注解上
  • ElementType.PACKAGE:允许作用在包上

@Inherited

是否允许子类继承该注解,注意,这不是说注解允许继承,而是如果你用注解标注了一个父类,是否允许这个父类的子类也自动拥有这个注解.

@Documented

注解是否应当被包含在 JavaDoc 文档中

编译器注解(java内置)

  • @Deprecated 表示弃用,编译这样的类/方法等编译器会给警告.它直接可以标注的东西很多,可以点进去看下@Target. 注意配合javadoc注释,告诉别人新的在哪里.
/**
  @deprecated This class is full of bugs. Use MyNewComponent instead.
*/
  • @Override 表示父类进行重写的方法
  • @SuppressWarnings 用来抑制编译器生成警告信息。

注解底层原理

奇怪的变量定义方式

先看一个接口public interface Annotation,在它的doc描述中有一句The common interface extended by all annotation types,也就是所有注解都会继承这个接口.那么注解其实也是接口,它在运行时会有代理类,这也就解释了,为什么定义注解使用的变量看起来会有点奇怪,居然要定义个方法.

@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {
    String helloKey() default "helloValue";
}

因为注解实际会有代理类,所以定义的属性确实是方法,只不过方法名字和属性名相同,属性值由方法return. 这里可能有人会问,为什么不直接定义变量,因为java用接口实现注解,接口无法定义实例变量.

代理

其实后面就是代理的事情了,默认是JDK的动态代理.
可以使用System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");来在当前工程中保存JDK生成的动态代理类.
看一下代理类

public final class $Proxy1 extends Proxy implements Hello {
    // equals
    private static Method m1;
    // toString
    private static Method m2;
    // annotationType
    private static Method m4;
    // hashCode
    private static Method m0;
    // 注解定义的属性对应的方法
    private static Method m3;
		
    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.learn.java.annotation.Hello").getMethod("annotationType");
            m3 = Class.forName("com.learn.java.annotation.Hello").getMethod("helloKey");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    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 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);
        }
    }

    public final String helloKey() 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类的实际逻辑都是交给InvocationHandler h的invoke方法处理的,
invoke(this,方法Method对象,方法参数列表),这是JDK代理的通用逻辑,代理后的实际逻辑在对应的handler. 对于注解,它的处理handler是AnnotationInvocationHandler.
同时,我们可以看到代理类Proxy对四个方法是有特殊处理的,这四个方法的实际逻辑也在对应的handler.如果没有这样操作的话,其实代理类会使用Object的实现或者找不到.

  • private static Method m1;// equals
  • private static Method m2;// toString
  • private static Method m4;// annotationType
  • private static Method m0;// hashCode

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;
    // var1是Proxy$1代理类的this,var2是传进来的方法对象,var3是方法参数
    // 我们只关注var2就好,别的没用到
    public Object invoke(Object var1, Method var2, Object[] var3) {
        // 方法名
        String var4 = var2.getName();
        // 方法参数
        Class[] var5 = var2.getParameterTypes();
        // equals特殊处理
        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;
            // 这里判断方法名的hashcode
            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;
                    }
            }
        }
    }
}		



可以看到,如果是4种特殊方法,那么var7会被设成对应的值,直接调用invoker中复写的方法. 如果是equals在上边就直接判断了.
如果不是,说明方法是注解自定义的方法,其实也就是注解的参数.
会从memberValues中取得,它是一个Map<String, Object>,会存储当前注解的属性名(方法名)为Key,以及对应的值.

举个例子

@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {
    String helloKey() default "helloValue";
}

那么memberValues存放的值是

当然,这是由于虚拟机读取到注解的Retention是Runtime.如果不是运行时的注解,那么不会有自己的AnnotationInvocationvandler.
注意虚拟机会给每一个运行时注解生成一个AnnotationInvocationvandler类的实例,type属性是注解的类型.

一个注解,多个地方使用也就是有多个实例,也会有多个AnnotationInvocationvandler类的实例