知其然,知其因此然html
在上一篇《反射從入門到精通之深刻了解Class類》,咱們深刻分析了一下 Class 類的原理。在本篇文章,咱們分析一下 Constructor 使用方法的原理。c++
經過反射調用構造函數有兩種方法:web
具體能夠詳見《反射從0到入門》,知道了這些咱們深刻了解下 Constructor 中的 newInstance(Object … initarges) 方法。app
想要了解原理,第一步就是要看懂 jdk 的註釋,newInstance 的註釋以下:ide
(打擾了,看不懂這個,全劇終。。。)svg
別走,我來給大家翻譯(Google 翻譯真香)函數
使用 Constructor 表明構造函數,根據參數建立而且初始化一個實例。各個參數將自動拆箱以匹配原始形式參數,而且原始參數和引用參數必須根據須要進行方法調用轉換。ui
要獲取無參構造函數,參數長度能夠爲 0 或者是 nullurl
調用非靜態內部類,參數該。。。(此處不翻譯)spa
訪問經過而且參數檢查成功,將繼續進行實例化。若是構造函數的聲明類沒有初始化,須要初始化
構造函數完成,返回新建立而且初始化好的實例
根據小李這段粗糙的翻譯中,能夠獲得下面幾個關鍵的內容:
知道了這些咱們來解讀一下 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("屬性名稱") 來獲取屬性值。
根據條件獲取 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 方法去建立實例。
咱們回顧下前面的內容:
首先咱們根據 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() 方法來建立實例。
反射相關文章:
公衆號:Java知識學堂,裏面有我最近整理的反射相關內容,但願能對你們有所幫助。