侧边栏壁纸
博主头像
Sir丶雨轩博主等级

一个90的萌新码农,热爱编程热爱生活 编程交流群:875477818

  • 累计撰写 53 篇文章
  • 累计创建 93 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

Java注解底层实现原理

Sir丶雨轩
2022-08-17 / 0 评论 / 0 点赞 / 195 阅读 / 1,236 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-08-17,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

想必大家在写项目中会碰到各种各样的注解,那注解到底是个什么东西呢?类?接口?还是抽象类?
注解也叫声明式接口,那真的是接口吗?
通过Idea查看类关系的功能(Ctrl+Shift+Alt+U)可以看到DemoAnnotation继承了Annotation接口
image

Annotation 可以在jdk包里面找到,它是所有注解的父接口
image-1660723041927

现在我们知道 注解是一个继承了Annotation的东西,那么@DemoAnnotation 到底是啥就不言而喻了。

然后我们去字节码中看看,自定义注解之后,我们需要在某个时刻将注解取出来,好取出来注解里面的值。
image-1660725969986
这里通过 Test.class.getDeclaredAnnotation(DemoAnnotation.class);方法 获取到注解DemoAnnotation 的实例 demoAnnotation,然后调用其demoAnnotation.value()方法。

在字节码中 ,可以看到调用的指令为 INVOKEINTERFACE指令

在jvm中 方法调用的指令有如下四种。
image-1660726048329

因此,java编译器认为 value()方法为一个接口方法, 因为如果是抽象类,那么就是用的invokevirual指令 。

截止目前,我们知道了注解是一个接口,一个继承自Annotation的接口。 里面每一个属性,其实就是接口的一个抽象方法。

那么新的问题来了,如果注解是接口,那么其何时实例化,怎么实例化?
我们是通过 Test.class.getDeclaredAnnotation(DemoAnnotation.class); 来获取到注解的实例的,那么看这个方法。
image-1660726300965

发现返回的实例名称 是$Proxy1, 很明显是一个代理对象。
里面还有一个叫AnnotationInvocationHandler的类,是不是很眼熟?

这个就是注解的代理逻辑封装

image-1660726344916

jdk的动态代理,需要一个Proxy类(jdk提供的用于生成对象的类),一个实现了InvocationHandler接口的类(用于封装代理逻辑的类)

static class AnnotationInvocationHandler implements InvocationHandler{
  
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   //处理注解的解析
    }
     
  }

因此 AnnotationInvocationHandler里面就封装了 注解的代理逻辑。

使用的时候
使用Proxy.newProxyInstance()

  public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

newProxyInstance,方法有三个参数:

loader: 用哪个类加载器去加载代理对象

interfaces:动态代理类需要实现的接口

h:动态代理方法在执行时,会调用h里面的invoke方法去执行

总结:
注解@interface 是一个实现了Annotation接口的 接口, 然后在调用getDeclaredAnnotations()方法的时候,返回一个代理$Proxy对象,这个是使用jdk动态代理创建,使用Proxy的newProxyInstance方法,传入接口 和InvocationHandler的一个实例(也就是 AnotationInvocationHandler ) ,最后返回一个实例。

那么该方法在何处被调用?
sun.reflect.annotation.AnnotationParser#annotationForMap
image-1660726397690
在这里 jdk动态代理的newProxyInstance方法返回的代理对象
image-1660726407297
那么注解实例化之后,值从哪里取的呢? 或者说,一开始传给注解的参数,存储到了哪?

那么分析流程
java.lang.Class#getDeclaredAnnotation 第一步 获取注解
java.lang.Class#createAnnotationData 第二步 创建注解实例
sun.reflect.annotation.AnnotationParser#parseAnnotations 第三步 解析注解

这一步有个关键点

方法getRawAnnotations() 获取原始批注 也是native方法。
image-1660726419703
还有一个方法getConstantPool() 获取常量池 也是native方法
image-1660726430147
然后经过sun.reflect.annotation.AnnotationParser#parseAnnotation2
这个类的加工转换
到要创建实例的时候
sun.reflect.annotation.AnnotationParser#annotationForMap

该方法返回的入参, 里面有整个Test2这个注解类的信息 ,这一步就获取了注解里面的值,存储在memberDefaults 这个hashmap里面。
image-1660726447540
因此,注解是将参数信息存储到了class文件的常量池里面,在创建实例的时候,会通过getConstantPool()获取出来,是一个byte[]流,需要进行转换。

最终总结:
注解@interface 是一个实现了Annotation接口的 接口, 然后在调用getDeclaredAnnotations()方法的时候,返回一个代理$Proxy对象,这个是使用jdk动态代理创建,使用Proxy的newProxyInstance方法时候,传入接口 和InvocationHandler的一个实例(也就是 AnotationInvocationHandler ) ,最后返回一个代理实例。

期间,在创建代理对象之前,解析注解时候 从该注解类的常量池中取出注解的信息,包括之前写到注解中的参数,然后将这些信息在创建 AnnotationInvocationHandler时候 ,传入进去 作为构造函数的参数。
image-1660726459671
image-1660726463482
当调用该代理实例的获取值的方法时,就会调用执行AnotationInvocationHandler里面的逻辑,将之前存入的注解信息 取出来。
image-1660726472664

0

评论区