深入理解
Java
中的反射
反射的概念
反射的原理
反射的主要用途
反射的運用
獲得Class物件
判斷是否是某個類的例項
建立例項
獲取方法
獲取構造器資訊
獲取類的成員變數資訊
呼叫方法
利用反射建立陣列
invoke方法
invoke執行過程
許可權檢查
呼叫MethodAccessor的invoke方法
JVM層invoke0方法
Java版的實現
invoke總結
反射注意點
反射的概念
反射:Refelection,反射是Java的特徵之一,允許執行中的Java程式獲取自身資訊,並可以操作類或者物件的內部屬性透過反射,可以在執行時獲得程式或者程式中的每一個型別的成員或成成員的資訊程式中的物件一般都是在編譯時就確定下來,Java反射機制可以動態地建立物件並且呼叫相關屬性,這些物件的型別在編譯時是未知的也就是說,可以透過反射機制直接建立物件,即使這個物件型別在編譯時是未知的
Java反射提供下列功能:在執行時判斷任意一個物件所屬的類在執行時構造任意一個類的物件在執行時判斷任意一個類所具有的成員變數和方法,可以透過反射呼叫private方法在執行時呼叫任意一個物件的方法
反射的原理
反射的核心:JVM在執行時才動態載入類或者呼叫方法以及訪問屬性,不需要事先(比如編譯時)知道執行物件是什麼
類的載入:Java反射機制是圍繞Class類展開的首先要了解類的載入機制:JVM使用ClassLoader將位元組碼檔案,即class檔案載入到方法區記憶體中
ClassLoader類根據類的完全限定名載入類並返回一個Class物件
ReflectionData:為了提高反射的效能,必須要提供快取class類內部使用一個useCaches靜態變數來標記是否使用快取這個值可以透過外部的sun。reflect。noCaches配置是否禁用快取class類內部提供了一個ReflectionData內部類用來存放反射資料的快取,並聲明瞭一個reflectionData域由於稍後進行按需延遲載入並快取,所以這個域並沒有指向一個例項化的ReflectionData物件
點選載入圖片
反射的主要用途
反射最重要的用途就是開發各種通用框架很多框架都是配置化的,透過XML檔案配置Bean為了保證框架的通用性,需要根據配置檔案載入不同的物件或者類,呼叫不同的方法要運用反射,執行時動態載入需要載入的物件
示例:在運用Struts 2框架的開發中會在struts。xml中配置Action
配置檔案與Action建立對映關係
當View層發出請求時,請求會被StrutsPrepareAndExecuteFilter攔截
StrutsPrepareAndExecuteFilter會動態地建立Action例項請求login。actionStrutsPrepareAndExecuteFilter會解析struts。xml檔案檢索action中name為login的Action根據class屬性建立SimpleLoginAction例項使用invoke方法呼叫execute方法
反射是各種容器實現的核心
反射的運用
反射相關的類在StrutsPrepareAndExecuteFilter包
反射可以用於:
判斷物件所屬的類
獲得class物件
構造任意一個物件
呼叫一個物件
獲得Class物件
使用Class類的forName靜態方法
直接獲取一個物件的class
呼叫物件的getClass方法
判斷是否是某個類的例項
一般來說,使用instanceof關鍵字判斷是否為某個類的例項
在反射中,可以使用Class物件的isInstance方法來判斷是否為某個類的例項,這是一個native方法
建立例項
透過反射生成物件的例項主要有兩種方式:
使用Class物件的newInstance方法來建立Class物件對應類的例項
先透過Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance方法來建立例項:可以用指定的構造器構造類的例項
獲取方法
獲取Class物件的方法集合,主要有三種方法:
getDeclaredMethods:返回類或介面宣告的所有方法:包括公共,保護,預設(包)訪問和私有方法不包括繼承的方法
getMethods:返回某個類所有的public方法包括繼承類的public方法
getMethod:返回一個特定的方法第一個引數:方法名稱後面的引數:方法的引數對應Class的物件
透過getMethods獲取的方法可以獲取到父類的方法
點選載入圖片
獲取構造器資訊
透過Class類的getConstructor方法得到Constructor類的一個例項
Constructor類中newInstance方法可以建立一個物件的例項
newInstance方法可以根據傳入的引數來呼叫對應的Constructor建立物件的例項
獲取類的成員變數資訊
getFileds:獲取公有的成員變數
getDeclaredFields:獲取所有已宣告的成員變數,但是不能得到父類的成員變數
呼叫方法
從類中獲取一個方法後,可以使用invoke來呼叫這個方法
利用反射建立陣列
陣列是Java中一種特殊的資料型別,可以賦值給一個Object Reference
利用反射建立陣列的示例
Array類是java。lang。reflect。Array類,透過Array。newInstance建立陣列物件
newArray方法是一個native方法,具體實現在HotSpot JVM中,原始碼如下
Array類的set和get方法都是native方法,具體實現在HotSpot JVM中,對應關係如下:set:Reflection::array_setget:Reflection::array_get
invoke方法
在Java中很多方法都會呼叫invoke方法,很多異常的丟擲多會定位到invoke方法
invoke執行過程
invoke方法用來在執行時動態地呼叫某個例項的方法,實現如下
許可權檢查
AccessibleObject類是Field,Method和Constructor物件的基類:提供將反射的物件標記為在使用時取消預設Java語言訪問控制檢查的能力
invoke方法會首先檢查AccessibleObject的override屬性的值:override預設值為false:表示需要許可權呼叫規則,呼叫方法時需要檢查許可權也可以使用setAccessible設定為trueoverride如果值為true:表示忽略許可權規則,呼叫方法時無需檢查許可權也就是說,可以呼叫任意private方法,違反了封裝
如果override屬性為預設值false,則進行進一步的許可權檢查
首先用Reflection。quickCheckMemberAccess(clazz, modifiers)方法檢查方法是否為public
1。1 如果是public方法的話,就跳過本步1。2 如果不是public方法的話,就用Reflection。getCallerClass方法獲取呼叫這個方法的Class物件,這是一個native方法
獲取Class物件caller後使用checkAccess方法進行一次快速的許可權校驗,checkAccess方法實現如下
首先先執行一次快速校驗,一旦Class正確則許可權檢查透過;如果未透過,則建立一個快取,中間再進行檢查
如果上面所有的許可權檢查都未透過,將會執行更詳細的檢查
用
Reflection。ensureMemberAccess方法繼續檢查許可權。若檢查透過就更新快取,這樣下一次同一個類呼叫同一個方法時就不用執行許可權檢查了,這是一種簡單的快取機制
由於JMM的happens-before規則能夠保證快取初始化能夠在寫快取之間發生,因此兩個cache不需要宣告為volatile
檢查許可權的工作到此結束。如果沒有透過檢查就會丟擲異常,如果沒有透過檢查就會到下一步
點選載入圖片
呼叫MethodAccessor的invoke方法
Method。invoke不是自身實現反射呼叫邏輯,而是透過sun。refelect。MethodAccessor來處理
Method物件的基本構成:每個Java方法有且只有一個Method物件作為root,相當於根物件,對使用者不可見當建立Method物件時,程式碼中獲得的Method物件相當於其副本或者引用root物件持有一個MethodAccessor物件,所有獲取到的Method物件都共享這一個MethodAccessor物件必須保證MethodAccessor在記憶體中的可見性
root物件及其宣告
MethodAccessor
MethodAccessor是一個介面,定義了invoke方法,透過Usage可以看出MethodAccessor的具體實現類:
sun。reflect。DelegatingMethodAccessorImpl
sun。reflect。MethodAccessorImpl
sun。reflect。NativeMethodAccessorImpl
第一次呼叫Java方法對應的Method物件的invoke方法之前,實現呼叫邏輯的MethodAccess物件還沒有建立
第一次呼叫時,才開始建立MethodAccessor並更新為root,然後呼叫MethodAccessor。invoke完成反射呼叫
methodAccessor例項由reflectionFactory物件操控生成,reflectionFactory是在AccessibleObject中宣告的
sun。reflect。ReflectionFactory方法
實際的MethodAccessor實現有兩個版本,一個是Java版本,一個是native版本,兩者各有特點:初次啟動時Method。invoke和Constructor。newInstance方法採用native方法要比Java方法快3-4倍啟動後native方法又要消耗額外的效能而慢於Java方法Java實現的版本在初始化時需要較多時間,但長久來說效能較好
這是HotSpot的最佳化方式帶來的效能特性:
跨越native邊界會對最佳化有阻礙作用
為了儘可能地減少效能損耗,HotSpot JDK採用inflation方式:Java方法在被反射呼叫時,開頭若干次使用native版等反射呼叫次數超過閾值時則生成一個專用的MethodAccessor實現類,生成其中的invoke方法的位元組碼以後對該Java方法的反射呼叫就會使用Java版本
ReflectionFactory。newMethodAccessor生成MethodAccessor物件的邏輯:native版開始會會生成NativeMethodAccessorImpl和DelegatingMethodAccessorImpl兩個物件
DelegatingMethodAccessorImpl
DelegatingMethodAccessorImpl物件是一箇中間層,方便在native版與Java版的MethodAccessor之間進行切換
native版MethodAccessor的Java方面的宣告:sun。reflect。NativeMethodAccessorImpl
每次NativeMethodAccessorImpl。invoke方法被呼叫時,程式呼叫計數器都會增加1,看看是否超過閾值
如果超過則呼叫MethodAccessorGenerator。generateMethod來生成Java版的MethodAccessor的實現類
改變DelegatingMethodAccessorImpl所引用的MethodAccessor為Java版
經由DelegatingMethodAccessorImpl。invoke呼叫到的就是Java版的實現
點選載入圖片
JVM層invoke0方法
invoke0方法是一個native方法,在HotSpot JVM裡呼叫JVM_InvokeMethod函式
openjdk/hotspot/src/share/vm/prims/
jvm
。cpp
關鍵部分為Reflection::invoke_method:openjdk/hotspot/src/share/vm/runtime/reflection。cpp
Java的物件模型:klass和oop
Java版的實現
Java版MethodAccessor的生成使用MethodAccessorGenerator實現
運用了asm動態生成位元組碼技術-
sun。reflect。ClassFileAssembler
invoke總結
invoke方法的過程:
MagicAccessorImpl:原本Java的安全機制使得不同類之間不是任意資訊都可見,但JDK裡面專門設了個MagicAccessorImpl標記類開了個後門來允許不同類之間資訊可以互相訪問,由JVM管理
@CallerSensitive註解
用@CallerSensitive註解修飾的方法從一開始就知道具體呼叫此方法的物件不用再經過一系列的檢查就能確定具體呼叫此方法的物件實際上是呼叫sun。reflect。Reflection。getCallerClass方法
Reflection類位於呼叫棧中的0幀位置sun。reflect。Reflection。getCallerClass方法返回呼叫棧中從0幀開始的第x幀中的類例項該方法提供的機制可用於確定呼叫者類,從而實現“感知呼叫者(Caller Sensitive)”的行為即允許應用程式根據呼叫類或呼叫棧中的其它類來改變其自身的行為
反射注意點
反射會額外消耗系統資源,如果不需要動態地建立一個物件,就不要使用反射
反射呼叫方法時可以忽略許可權檢查。可能會破壞封裝性而導致安全問題
點選載入圖片