反射之深入理解Constructor原理

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

反射之深入理解Constructor原理

Java知识学堂   2019-12-24 我要评论

知其然,知其所以然

0. 前言

在上一篇《反射从入门到精通之深入了解Class类》,我们深入分析了一下 Class 类的原理。在本篇文章,我们分析一下 Constructor 使用方法的原理。

1. Constructor

通过反射调用构造函数有两种方法:

  • 调用无参构造函数:Class.newInstance()
  • 调用带参数的构造函数:
  • 通过 Class 类获取 Constructor
  • 调用 Constructor 中的 newInstance(Object … initarges) 方法

具体可以详见《反射从0到入门》,知道了这些我们深入了解下 Constructor 中的 newInstance(Object … initarges) 方法。

1.1 newInstance

想要了解原理,第一步就是要看懂 jdk 的注释,newInstance 的注释如下:

(打扰了,看不懂这个,全剧终。。。)

别走,我来给你们翻译(Google 翻译真香)

使用 Constructor 代表构造函数,根据参数创建并且初始化一个实例。各个参数将自动拆箱以匹配原始形式参数,并且原始参数和引用参数必须根据需要进行方法调用转换。

要获取无参构造函数,参数长度可以为 0 或者是 null

调用非静态内部类,参数该。。。(此处不翻译)

访问通过并且参数检查成功,将继续进行实例化。如果构造函数的声明类没有初始化,需要初始化

构造函数完成,返回新创建并且初始化好的实例

根据小李这段粗糙的翻译中,可以得到下面几个关键的内容:

  • 根据参数创建初始化实例,参数有匹配的规则;
  • 获取无参构造函数,参数长度可以为 0 或者 null;
  • 有访问权限并且对参数进行检查,需要获取到构造函数的声明类;

知道了这些我们来解读一下 newInstance() 的源码,看下图:

源码可以拆分为三块:

  1. 校验权限:校验权限就不在此分析了,大家可以自行查看源码
  2. 获取构造函数的声明类
  3. 创建实体

获取构造函数的声明类

构造函数声明类 ConstructorAccessor 是一个接口,如下图所示:

查看下接口的实现类如下结构(虚线代表实现接口,蓝色线代表继承,那白线是什么鬼?)

从图中可知实现类都是继承了 ConstructorAccessorImpl 抽象类,并且实现了 newInstance() 方法。

那到底使用哪个实现类那呢?咱们继续往下看

如果 ConstructorAccessor 已经被创建了,获取并赋值。如果没有则通过 newConstructorAccessor 方法创建 ConstructorAccessor。newConstructorAccessor 方法如下:

newConstructorAccessor 分为三部分:

  1. 检查是否初始化

这是反射工厂(ReflectionFactory)检查初始化状态,如果没有初始化会进行下面用红线圈上的操作。

那大概猜一下这块是做什么呢?

首先,inflation 字面理解是通胀或者膨胀,那 noInflation 按字面理解也是不膨胀。

Threshold 字面理解是阈值,inflationThreshold 按字面理解是通胀阈值,就是一个通胀的界限值。

按照字面理解可知 noInflation 来判断是否通胀,inflationThreshold 是一个通胀的界限值。

问问度娘,验证下咱们的结果:

JNI(Java Native Interface),通过使用 Java 本地接口书写程序,可以确保代码在不同的平台上方便移植。

猜的差不多,JVM 有两种方法来访问有关反射的类的信息,可以使用 JNI 读取器或者 Java 字节码存取器。inflationThreshold 是使用 JNI 存取器的次数,值为 0 表示永不从 JNI 存取器读取。如果想强制使用 Java 字节码存取器,可以设置 noInflation 为 true。

inflationThreshold 默认值是 15,如果不对 inflationThreshold 进行修改,JVM 访问反射的类的信息会先从 JNI 存取器读取 15次之后才会使用 Java 字节码存取器

这就可以解释通为什么要有一个初始化检测的操作了。

从这部分可以学到一些小知识:

我们可以使用 -D= 来设置系统属性,通过 System.getProperty("属性名称") 来获取属性值。

  1. 获取当前类的 Class 实例
  1. 根据条件获取 ConstructorAccessor

    这么多 if 条件判断,不要慌,我来帮你分析一波:

  • 第 1 步,校验在第二步获取的 Class 实例是不是抽象类,如果是抽象类就抛出异常。

  • 第 2 步,判断是否是 Class 实例,因为 Class 实例的构造函数是 private,所以这块也需要抛出异常。

  • 第 3 步,判断这个 Class 实例是否继承 ConstructorAccessorImpl,如果是父子关系,就调用 BootstrapConstructorAccessorImpl 创建 ConstructorAccessor,这个方法是调用 native 方法,底层用 c++ 实现的本地接口。

  • 第 4 步,如果 noInflation 为 true 并且 Class 实例不是匿名的,需要调用 MethodAccessorGenerator.generateConstructor() 创建 ConstructorAccessor,具体的细节就不分析了,原理还是通过读取二进制文件,将 Class 实例加载进来,然后根据一些条件获取到想要的 Constructor。

  • 第 5 步,上面的条件都不满足,就调用 NativeConstructorAccessorImpl,看下这个方法的源码:

    ​ 调用次数和 inflationThreshold 比较,如果大于inflationThreshold(默认是 15 次),调用的方法是不是和第四步是相同的。

    ​ 如果小于等于 inflationThreshold ,就要调用 newInstance0 方法,newInstance0 是 native 方法,调用的就是本地接口。

    ​ 其实第一步初始化的时候就是为了在这里做铺垫呢。

    ​ 到这里还没有完事,还有一个 DelegatingConstructorAccessorImpl 方法。

    ​ 那这一块使用了一手代理模式,把 NativeConstructorAccessorImpl 放入到 DelegatingConstructorAccessorImpl 的 delegate 中。newInstance 调用的是 delegate 的 newInstance 方法。

    ​ 还记得我最开始问白线是做什么的,这回解惑了吧。

内容有点多,我画个图带你们梳理一下:

图片链接

创建实例

上一步获取到了 ConstructorAccessor 的实现类,直接调用 newInstance 方法去创建实例。

2. 总结

我们回顾下前面的内容:

首先我们根据 jdk 提供的注释知道 newInstance 可以根据参数进行初始化并返回实例,想要获取实例必须要获取到构造函数的声明类 ConstructorAccessor ,随后我们就深入分析了 ConstructorAccessor 。

ConstructorAccessor 有一个抽象类 ConstructorAccessorImpl,其它的实现类需要继承 ConstructorAccessorImpl,分别是下面几个实现类:

  • InstantiationExceptionConstructorAccessorImpl:将异常信息存起来,调用 newInstance 会抛出 InstantiationException 异常。

  • BootstrapConstructorAccessorImpl:当需要创建的 Class 实例和 ConstructorAccessorImpl 是父子关系,就要返回 BootstrapConstructorAccessorImpl,调用的是底层的方法,通过 C++ 编写。

  • SerializationConstructorAccessorImpl:也是一个抽象类,当 JVM 从 Java字节码进行读取,会返回这个实现类。

  • NativeConstructorAccessorImpl:调用本地接口方法创建 ConstructorAccessor ,需要根据调用次数和inflationThreshold 做比较,inflationThreshold 的默认值是 15,可以通过-Dsun.reflect.inflationThreshold=来修改默认值。

    当调用次数大于 15次的时候,JVM 从 Java字节码进行获取。反之,从本地接口进行获取。

  • DelegatingConstructorAccessorImpl:代理类,是 NativeConstructorAccessorImpl 的父类。

获取到了 ConstructorAccessor,通过调用 newInstance() 方法来创建实例。

3. 彩蛋

反射相关文章:

《反射从0到入门》

《反射从入门到精通之深入了解Class类》

公众号:Java知识学堂,里面有我最近整理的反射相关内容,希望能对大家有所帮助。

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们