java中的反射

反射,java老司機應該都對它不陌生,不管是在jvm還是一些大型的框架中,總是能看到它的身影,接下來我們就來看看java的反射到底是如何實現的。

前言

在分析具體實現之前我們還是先來看個反射具體使用實例:


使用案例

運行結果:


運行結果

從案例可以看出,我們通過反射來獲取對象定義的相關內容,同時還可以通過Method的invoke方法調用該對象任何方法。想想,如果在系統運行期間大量利用反射來操作對象,會不會有什么隱患呢?接下來我們就來看看java反射的具體實現。

Field的獲取

java的反射可通過Field來獲取某個類的屬性或者屬性值,Class類提供以下方法來獲取Field:

  • public Field[] getDeclaredFields():獲取類聲明的所有屬性,包括private屬性;

  • public Field getDeclaredField(String name):獲取類聲明的屬性名為name的屬性;

  • public Field[] getFields():獲取類聲明的所有public類型屬性;

  • public Field getField(String name):獲取類聲明的屬性名為name的public類型屬性。

接下來我們就以getDeclaredField方法為例,詳細分析其源碼實現。

getDeclaredField實現
getDeclaredField實現

從源碼可以看出:

  1. 校驗成員變量是否允許被訪問:
  • 允許被訪問,跳轉到步驟2;
  • 否則,拋出SecurityException異常;
  1. 調用searchFields方法獲取屬性名為name的field;
  2. 如果步驟2的field為null,拋出NoSuchFieldException異常,否則,返回該field。

獲取Field的邏輯主要在searchFields方法中實現,searchFields方法的第一個參數是通過privateGetDeclaredFields方法從緩存或者JVM獲取到的Fields列表,接下來我們來看看它們的相關實現。

privateGetDeclaredFields獲取Fields列表
privateGetDeclaredFields實現

從源碼可以看出,privateGetDeclaredFields方法首先通過reflectionData方法從緩存中獲取,如果從緩存中獲取不到,再調用Reflection.filterFields方法從JVM中獲取。接下來我們來看看reflectionData方法是如何實現數據緩存的。

reflectionData實現
reflectionData實現

從源碼可以看出:

  • 緩存數據結構:ReflectionData,緩存了Field、Method和Constructor等相關數據;

  • 整個reflectionData方法,首先是從緩存中獲取相關數據,需要注意的是,reflectionData對象是SoftReference類型的,該類型的數據在內存緊張時會被回收,如果該對象被回收,則通過newReflectionData方法重新創建一個新的對象,newReflectionData方法的相關源碼實現如下:


    newReflectionData實現
searchFields獲取指定Field
searchFields實現

從源碼可以看出,searchFields方法在找到指定的Field之后,會重新copy一份返回,當然如果沒有找到指定Field則返回null。

到這里為止,Field相關獲取的實現就告一段落,具體通過Field來操作對象相關部分源碼在本文就不做詳細講解了,有興趣的小伙伴可以自行閱讀源碼。接下來我們就繼續來看看Method是如何來獲取和調用的。

Method的獲取

同Field一樣,Class同樣提供四個方法來獲取Method:

  • public Method[] getDeclaredMethods():獲取類的所有方法,包括private類型方法;

  • public Method getDeclaredMethod(String name, Class<?>... parameterTypes):獲取類的指定方法名和參數的方法;

  • public Method[] getMethods():獲取類的所有public類型方法;

  • public Method getMethod(String name, Class<?>... parameterTypes):獲取類的指定方法名和參數的public類型方法。

接下來就以getDeclaredMethod方法為例,詳細分析Method的獲取的具體實現。

getDeclaredMethod實現
getDeclaredMethod實現

同getDeclaredField方法的流程一樣:

  1. 校驗方法是否允許被訪問:
  • 允許被訪問,跳轉到步驟2;
  • 否則,拋出SecurityException異常;
  1. 調用searchMethods方法獲取指定方法名和參數的method;
  2. 如果步驟2的method為null,拋出NoSuchMethodException異常,否則,返回該method。

接下來我們就來看看searchMethods方法相關的具體實現。

:由于searchMethods方法獲取指定Method同searchFields方法獲取指定Field實現基本一致,在這里就不在做詳細的分析。

privateGetDeclaredMethods獲取Method列表
privateGetDeclaredMethods實現

同privateGetDeclaredFields方法一樣,privateGetDeclaredMethods方法同樣通過reflectionData方法從緩存中獲取Method列表,如果從緩存到獲取不到,才會調用Reflection.filterMethods方法從JVM中獲取。

searchMethods獲取指定Method
searchMethods實現

同searchFields方法一樣,searchMethods方法在找到指定的Method之后,同樣會重新copy一份返回。

到這里為止,Method的獲取就簡單分析完了,在獲取到Method之后,我們可以通過其invoke方法來實現調用了,接下來我們就來看看invoke方法的具體實現。

Method的調用

invoke實現

從源碼可以看出:

  1. 校驗該方法是否允許被訪問,允許被訪問則跳轉到步驟2,否則,拋出IllegalAccessException異常;

  2. 獲取當前method的MethodAccessor對象ma,如ma為null,則調用acquireMethodAccessor方法獲取;

  3. 調用MethodAccessor對象的invoke方法來實現調用,整個invoke方法的核心也在這里。

接下來我們就來看看acquireMethodAccessor方法獲取MethodAccessor對象以及該對象的invoke方法的相關實現。

acquireMethodAccessor獲取MethodAccessor
acquireMethodAccessor實現

從源碼可以看出:

  • 如果root節點不為空,則從root節點獲取MethodAccessor對象,并且將該對象賦值給當前Method對象的methodAccessor屬性;

  • 如果root節點為null,或者root節點的methodAccessor對象為null時,調用reflectionFactory.newMethodAccessor方法為其創建一個新的MethodAccessor對象并返回。


MethodAccessor定義

從MethodAccessor的定義可以看出,MethodAccessor是一個接口,那么,上述MethodAccessor對象其實也就是MethodAccessor的子類的對象。

reflectionFactory.newMethodAccessor創建MethodAccessor

在開始分析newMethodAccessor方法的實現之前我們先來看看ReflectionFactory相關內容。

  • ReflectionFactory重要屬性

    ReflectionFactory相關屬性

    ReflectionFactory有兩個重要屬性:noInflation(默認值為false)和inflationThreshold(默認值為15),它們的重新設置可以通過checkInitted方法來完成,源碼如下圖所示:
    checkInitted實現

    從源碼標紅框的兩部分可以看出,noInflation和inflationThreshold可以通過參數-Dsun.reflect.inflationThreshold-Dsun.reflect.noInflation設置。

  • newMethodAccessor實現

    newMethodAccessor實現

    從源碼可以看出:

    • 如果noInflation == true時,調用MethodAccessorGenerator對象的generateMethod方法生成MethodAccessorImpl對象;

    • 否則,生成DelegatingMethodAccessorImpl對象并返回。

MethodAccessor.invoke方法實現

:以子類DelegatingMethodAccessorImpl的具體實現為例,具體分析invoke方法的相關實現。

DelegatingMethodAccessorImpl實現

DelegatingMethodAccessorImpl實現

從源碼可以看出,DelegatingMethodAccessorImpl對象就是一個代理對象,最終的invoke方法其實也就是調用NativeMethodAccessorImpl的invoke方法。

NativeMethodAccessorImpl實現

NativeMethodAccessorImpl實現

需要注意的是,如果當前invoke方法被調用的次數超過 ReflectionFactory.inflationThreshold,后續的調用就通過MethodAccessorGenerator對象的generateMethod方法生成MethodAccessorImpl對象,并將它賦值給delegate,這樣下次再調用Method.invoke時,調用的也就是新生成的MethodAccessor對象的invoke方法。

generateMethod實現

注:由于generateMethod實現代碼比較多,在這里我就不再貼源碼了,有興趣的小伙伴可以去openjdk看一下相關源碼實現。

generateMethod在生成MethodAccessorImpl對象時,會生成相應的字節碼并調用ClassDefiner.defineClass創建對應的對象。而每一次在調用ClassDefiner.defineClass創建對象時,都會生成一個類加載器,具體的源碼如下圖所示:


defineClass實現

需要注意的是,為什么每次在創建對象時都需要生成類加載器呢?這么做的主要原因也是為了讓這些生成的類可以被回收。稍微了解點兒gc的小伙伴可能都知道,類可以被回收只有在類的加載器可以被回收的情況下才會被回收,如果不每次生成新的類加載器,就可能會導致新創建的類一直不能被回收。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容

  • 引言:java的高級特性-反射一直是困擾自己的一個很大問題,今天專門花了半天再將java中的反射看了一遍,下面簡單...
    cp_insist閱讀 2,336評論 1 6
  • 什么是反射 Java 反射是可以讓我們在運行時獲取類的函數、屬性、父類、接口等 Class 內部信息的機制。 能做...
    紅燒排骨飯閱讀 544評論 0 1
  • 概述 Java 反射是可以讓我們在運行時獲取類的方法、屬性、父類、接口等類的內部信息的機制。也就是說,反射本質上是...
    absfree閱讀 6,257評論 3 15
  • 生活如戲 戲如生活 一直很想找一個這樣歷史故事 大概說的是: 從前,有一個能力不俗 忠心耿耿的副將,一直對他的將軍...
    梅紫醬閱讀 283評論 0 0
  • 怎么說 不管我再忙再不開心 也盡可能盡早回應 可是你在朋友圈點贊卻不回微信的行為 我確實不知道怎么了
    牧七沙閱讀 179評論 0 0