zoukankan      html  css  js  c++  java
  • 深入理解Constructor之newInstance方法

    知其然,知其所以然

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

  • 相关阅读:
    ABP 异常
    Vmware中安装的Ubuntu不能全屏问题解决
    centos7.4 文件权限
    webpack 入门(1)
    webpack(2) 概念
    centos7.4 rpm命令
    centos7.4 which、whereis、locate的使用
    centos7.4 find命令
    centos7.4 lsof用法
    centos7.4 用户和组的管理
  • 原文地址:https://www.cnblogs.com/ferryman/p/12089210.html
Copyright © 2011-2022 走看看