1. 程式人生 > >反射之深入理解Constructor原理

反射之深入理解Constructor原理

知其然,知其所以然

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