美文网首页
注解实际是一个接口?来了解一下底层原理

注解实际是一个接口?来了解一下底层原理

作者: 吾心仔 | 来源:发表于2021-07-02 14:03 被阅读0次

掘金同步账号 脑袋满满的问号

在使用注解的时候,可能会听人说注解实际上是一个接口,那有没有想过为什么?今天就让我们分析一波注解实现的底层原理,深入了解注解的实现。

了解注解

首先是不是要知道什么是注解吧,在这就不描述了,可以直接google或者百度了解。

所有的注解的直接父接口都是 java.lang.annotation.Annotation,那了解一下,我们可以直接看 Annotation 接口类


image.png

上面表示:所有注解类型扩展的通用接口,还要注意这个接口本身并没有定义注解类型。

注解实现分析

既然要分析,那例子必不可少。
先让我们创建一个简单的注解 @TestAnnotation

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

  String name() default "null";
}

并使用

@TestAnnotation(name = "main")
public class Main {
  public static void main(String[] args) {
    TestAnnotation annotation = Main.class.getAnnotation(TestAnnotation.class);
    System.out.println(annotation.name());
  }
}

那怎么去查看相应注解的实现呢?

让我们断点调试,获取到annotation对象

image.png

发现它实际上是一个代理对象$Proxy1。

那么这个代理对象是怎么获取的呢?

1、getAnnotation(Class<A> annotationClass),通过类型获取注解实例。

Main.class.getAnnotation(TestAnnotation.class)

2、AnnotationData annotationData(),获取注解的数据。

private Class.AnnotationData annotationData() {
        while (true) { // 重试循环
            Class.AnnotationData annotationData = this.annotationData;
            int classRedefinedCount = this.classRedefinedCount;
            if (annotationData != null &&
                    annotationData.redefinedCount == classRedefinedCount) {
                return annotationData;
            }
            // null 或旧的 annotationData -> 创建新实例
            Class.AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount);
            // 对比新旧数据
            if (Class.Atomic.casAnnotationData(this, annotationData, newAnnotationData)) {
                // 返回新的 AnnotationData
                return newAnnotationData;
            }
        }
    }

3、createAnnotationData(int classRedefinedCount),构建注解的数据。

image.png

4、AnnotationParser#parseAnnotations(byte[] var0, ConstantPool var1, Class<?> var2), 用于解析注解,这一步使用到字节码中常量池的索引解析,常量解析完毕会生成一个成员属性键值对作为下一个环节的入参,常量池的解析可以看AnnotationParser#parseMemberValue方法。

image.png

5、AnnotationParser#annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1),用于生成注解的动态代理类。

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

而由创建方法可知:

Proxy#newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);

最终生成一个的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;
    
    /**
    * 代码忽略
    */

    public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        // 使用 var7 给 Annotation 中的四个方法做标记
        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;
                }
            }
            // 如果当前调用的方法是Annotation中的四个方法的话,
            // AnnotationInvocationHandler 实例中已经定义好了这些方法的实现,直接调用。
            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                // 自定义方法处理 从我们的注解 map 中获取这个注解属性对应的值。
                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) {
                        // clone 返回内容
                        var6 = this.cloneArray(var6);
                    }
                    return var6;
                }
            }
        }
    }

呐,现在我们知道了注解底层使用了JDK原生的Proxy,那么如何查看代理类的源码,这里提供两种方式来输出Proxy类的源码:

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

[图片上传失败...(image-dfe842-1625205787494)]

两种方式选择一种即可。

运行后,根目录下会生成两个代理类

image.png

$Proxy0 实现了 Retention 接口

$Proxy1 实现了 TestAnnotation 接口

public final class $Proxy1 extends Proxy implements TestAnnotation {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy1(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        ···
    }

    public final String name() 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 String toString() throws  {
        ···
    }

    public final Class annotationType() throws  {
        ···
    }

    public final int hashCode() throws  {
        ···
    }
        
    // 静态代码块实例化方法实例,使用方法名称直接从map中取值,
    // 并不是想象中使用反射获取值,而是直接调用,十分高效的处理方式。
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.mrlsm.spring.demo.TestAnnotation").getMethod("name");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.mrlsm.spring.demo.TestAnnotation").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());
        }
    }
}

由上我们发现该$Proxy1代理类中的静态代码块实例化方法实例,使用方法名称直接从map中取值,并不是想象中使用反射获取值,而是直接调用,十分高效的处理方式。

小结

注解就是一个实现了Annotation接口的接口,然后在调用getAnnotation()方法的时候,返回一个代理$Proxy对象,这个是使用jdk动态代理创建,使用Proxy的newProxyInstance方法时候,传入接口 和InvocationHandler的一个实例(也就是 AnotationInvocationHandler ) ,最后返回一个代理实例,来进行值的获取与操作。

掘金:https://juejin.cn/user/4353721774650413

相关文章

  • Java自定义注解原理及实现

    本章主要内容:1.了解注解原理,2,自定义注解(根据实际应用自定义注解打印每个接口的请求日志) 一, 了解注解原理...

  • 注解实际是一个接口?来了解一下底层原理

    掘金同步账号 脑袋满满的问号[https://juejin.cn/user/4353721774650413] 在...

  • 注解的实现原理

    一、注解的底层实现原理 注解的底层也是使用反射实现的,我们可以自定义一个注解来体会下。注解和接口有点类似,不过申明...

  • 1、NIO实现Socket服务器

    JDK包装了统一的API;实际上底层就是调用了 Linux的 select、poll、epoll 接口。 底层原理...

  • Retrofit2框架原理分析

    大概原理通过java接口以及注解来描述网络请求,并用动态代理的方式,在调用接口方法前后(before/after)...

  • Retrofit2 + Rxjava2

    Retrofit2 · 大概原理通过java接口以及注解来描述网络请求,并用动态代理的方式,在调用接口方法前后(...

  • Java类集框架 —— HashSet、LinkedHashSe

    前言 HashSet实现了Set接口,它的底层是由HashMap来支持的。HashSet的元素实际上是存储在底层H...

  • SpringMVC常用的注解有哪些?

    注解原理: 注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们...

  • OC底层原理汇总

    OC底层原理(一).alloc实际调用流程分析OC底层原理(二).内存分配与内存对齐OC底层原理(三)、isa、对...

  • 注解的反射

    我们用Annotation接口来描述 注解这个东西,注解是一种特殊的接口,而接口是一种特殊的类 所以 注解可有抽象...

网友评论

      本文标题:注解实际是一个接口?来了解一下底层原理

      本文链接:https://www.haomeiwen.com/subject/wgweultx.html