深入理解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知識學堂,里面有我最近整理的反射相關內容,希望能對大家有所幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM