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

物件

Java高階工程師面試:Java中反射機制的理解!反射機制的使用原理

反射的主要用途

反射最重要的用途就是開發各種通用框架

很多框架都是配置化的,透過

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()

獲取的方法可以獲取到父類的方法

Java高階工程師面試:Java中反射機制的理解!反射機制的使用原理

獲取構造器資訊

透過

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

檢查許可權的工作到此結束。如果沒有透過檢查就會丟擲異常,如果沒有透過檢查就會到下一步

Java高階工程師面試:Java中反射機制的理解!反射機制的使用原理

呼叫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

版的實現

Java高階工程師面試: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)”的行為即允許應用程式根據呼叫類或呼叫棧中的其它類來改變其自身的行為

反射注意點

反射會額外消耗系統資源,如果不需要動態地建立一個物件,就不要使用反射

反射呼叫方法時可以忽略許可權檢查。可能會破壞封裝性而導致安全問題

Java高階工程師面試:Java中反射機制的理解!反射機制的使用原理

相關文章