In-depth reflection of the principle of understanding Constructor

Know, know why

0. Introduction

On a "reflection from entry to the master of insight into Class class" , our in-depth analysis of what Class principle of class. In this article, we analyze the Constructor using the principle of the method.

1. Constructor

There are two methods by reflection to invoke the constructor:

  • Call the no-argument constructor: Class.newInstance ()
  • Call the constructor with parameters:
  • By Class acquiring class Constructor
  • Call Constructor of newInstance (Object ... initarges) method

Specific can see "the reflection from 0 to Getting Started" , we know these deep knowledge of Constructor of newInstance (Object ... initarges) method.

1.1 newInstance

Want to know principle, the first step is to understand jdk comment, newInstance comments as follows:

(Excuse me, do not understand this whole end of the play ...)

Do not go, I'll give you the translation (Google translated so delicious)

Representative using Constructor constructor creates and initializes an example of a parameter. Each parameter is automatically unpacking parameters to match the form of the original, and the original parameter and the reference parameter have to convert the method call as needed.

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

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

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

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

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

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

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

源码可以拆分为三块:

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

获取构造函数的声明类

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

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

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

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

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

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知识学堂,里面有我最近整理的反射相关内容,希望能对大家有所帮助。

Guess you like

Origin www.cnblogs.com/ferryman/p/12089210.html