Java高階工程師面試:Java中反射機制的理解!反射機制的使用原理
深入理解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_set
get:
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)”的行為即允許應用程式根據呼叫類或呼叫棧中的其它類來改變其自身的行為
反射注意點
反射會額外消耗系統資源,如果不需要動態地建立一個物件,就不要使用反射
反射呼叫方法時可以忽略許可權檢查。可能會破壞封裝性而導致安全問題
相關文章
- 2021-09-09吃麵的區別,中國一口吞,韓國吃麵用剪刀,看完非洲的吃法沒胃口
- 2021-09-08渣男是怎樣跟女生聊天的? 這套攻略他們從不外洩, 男人不要太老實
- 2021-09-04早餐的藝術:生活是項鍊,美食是珍珠
- 2021-06-03孟婆湯摻水了:爸爸讓出生第2天的寶寶親一個,寶寶立馬來個飛吻
- 2021-05-13“我已經聽話結婚了,心裡藏個人怎麼了”