知其然,知其所以然
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() 的源碼,看下圖:

源碼可以拆分為三塊:
- 校驗權限:校驗權限就不在此分析了,大家可以自行查看源碼
- 獲取構造函數的聲明類
- 創建實體
獲取構造函數的聲明類
構造函數聲明類 ConstructorAccessor 是一個接口,如下圖所示:

查看下接口的實現類如下結構(虛線代表實現接口,藍色線代表繼承,那白線是什么鬼?)

從圖中可知實現類都是繼承了 ConstructorAccessorImpl 抽象類,並且實現了 newInstance() 方法。
那到底使用哪個實現類那呢?咱們繼續往下看

如果 ConstructorAccessor 已經被創建了,獲取並賦值。如果沒有則通過 newConstructorAccessor 方法創建 ConstructorAccessor。newConstructorAccessor 方法如下:

newConstructorAccessor 分為三部分:
- 檢查是否初始化
這是反射工廠(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("屬性名稱") 來獲取屬性值。
- 獲取當前類的 Class 實例

-
根據條件獲取 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. 彩蛋
反射相關文章:
公眾號:Java知識學堂,里面有我最近整理的反射相關內容,希望能對大家有所幫助。
