• 重新认识Java注解


    重新认识Java注解

    今天Debug看源码的时候,无意间看到这么个东西

    图1

    首先承认我的无知,看到这个我很惊诧。

    也勾起了我的好奇心,于是有了这篇认知记录。

    下面就来重新认识下注解吧!

    注解的本质

    关于运行时注解的信息,会在.class文件中,并且最终以运行时数据结构存储在方法区,也知道我们是可以通过Class对象或者Method对象,来获取其相应的注解信息的。

    不过确实没有意识到,或者说根本就没有去猜想其背后的实现,也许是直接使用来解析注解的机会比较少吧。

    现在才认识到,原来我们定义的注解, 最终使用的时候,都是以一个代理类的方式与相应的Class或者Method对象绑定到一起。

    所有的注解,其实都是接口Annotation子接口,而每一个@interface的声明,最后其实就是一个普通的interface罢了!下面请看

    public @interface AnnotationDemo {
    
        int value();
    
        int name ();
    
    }
    
    
    
    public interface com.example.demo.anno.AnnotationDemo extends java.lang.annotation.Annotation {
      	
      	public abstract int value();
    
     	public abstract int name();
    }
    
    

    从上面对一个注解类的反编译结果就能看出来,它其实就是一个普通的接口类

    从接口到实例

    我们是如何查找到一个类定义的那些注解然后去使用呢?

    答案是:从Class对象中,我们可以获取所有的信息

    一个Class 的所有Annotation代理类被封装到一个私有静态类AnnotationData

    private static class AnnotationData {
        // 一个Map 映射 具体的Annotation Class 和其代理类对象
        final Map<Class<? extends Annotation>, Annotation> annotations;
        final Map<Class<? extends Annotation>, Annotation> declaredAnnotations;
    
        // Value of classRedefinedCount when we created this AnnotationData instance
        final int redefinedCount;
    
        AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations,
                       Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
                       int redefinedCount) {
            this.annotations = annotations;
            this.declaredAnnotations = declaredAnnotations;
            this.redefinedCount = redefinedCount;
        }
    }
    

    Class类中有一个成员变量

        private volatile transient AnnotationData annotationData;
    

    而具体的创建动态代理对象的操作,则是懒加载的方式

      public Annotation[] getAnnotations() {
            // 调用 Class#annotationData方法
            return AnnotationParser.toArray(annotationData().annotations);
      }
    
    private AnnotationData annotationData() {
        while (true) { // retry loop
            AnnotationData annotationData = this.annotationData;
            int classRedefinedCount = this.classRedefinedCount;
            // 如果已经初始化,并且这个类的redefinedCount和创建此AnnotationData对象时一致
            // 则无需重新创建AnnotationData对象
            // java.lang.instrument.Instrumentation#redefineClasses允许在运行时,重新定义类
            if (annotationData != null &&
                annotationData.redefinedCount == classRedefinedCount) {
                return annotationData;
            }
            // null or stale annotationData -> optimistically create new instance
            // 为null 或者已经过时了,创建一个新的实例
            AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount);
            // try to install it
            // 使用Unsafe CAS去更新字段 annotationData,直至成功
            if (Atomic.casAnnotationData(this, annotationData, newAnnotationData)) {
                // successfully installed new AnnotationData
                return newAnnotationData;
            }
        }
    }
    

    创建AnnotationData时,是通过一些native方法,获取类相关的annotation元信息的byte[]数组表示,然后解析出注解接口对应的Class对象,最后去通过Jdk Dynamic Proxy动态代理来创建对象

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

    这样最终就创建了一个注解接口的代理类

    总结

    注解类就是一个普通的接口类,最终在使用时,会创建相应的代理对象,用来获取定义在注解上的一些元数据信息。

    为什么要用接口?

    我的理解是,接口简单、简洁,所有的方法都是抽象方法,属性都是静态常量,而我们的添加在注解上的一些信息,通常都是一些值,并不需要方法体来去做些什么。

    不过使用接口来实现注解,就会有个问题,接口的字段都是静态常量,不能修改,所以注解里定义的都是方法,而动态代理类就是为了能在运行时,调用注解定义的方法,就能获取我们定义在注解上的值。

    到这里,对注解的实现已经有了一个大概的认识,不过一些细节,并没有深究,能力有限,待需要时,有机会和能力再去深究。

  • 相关阅读:
    Java 数组
    【转】Centos 设置IP地址的几种方式
    【转】CentOS 使用yum命令安装出现错误提示”could not retrieve mirrorlist http://mirrorlist.centos.org ***”
    【转】CentOS图形界面的开启与关闭
    【转】linux Centos 6.5 安装桌面环境GNOME
    VirtualBox 更改主机和虚拟机之间的鼠标切换热键
    【转】Virtualbox虚拟机配置安装CentOS 6.5图文教程
    0622 python 基础05
    0617 python 基础04
    0610 python 基础03
  • 原文地址:https://www.cnblogs.com/heartlake/p/12825995.html
Copyright © 2020-2023  润新知