反射理解
文章首發(fā)于【博客園-陳樹(shù)義】,點(diǎn)擊跳轉(zhuǎn)到原文《大白話(huà)說(shuō)Java反射:入門(mén)、進(jìn)階、原理》
反射之中包含了一個(gè)「反」字,所以想要解釋反射就必須先從「正」開(kāi)始解釋。
一般情況下,我們使用某個(gè)類(lèi)時(shí)必定知道它是什么類(lèi),是用來(lái)做什么的。于是我們直接對(duì)這個(gè)類(lèi)進(jìn)行實(shí)例化,之后使用這個(gè)類(lèi)對(duì)象進(jìn)行操作。
Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);
上面這樣子進(jìn)行類(lèi)對(duì)象的初始化,我們可以理解為「正」。
而反射則是一開(kāi)始并不知道我要初始化的類(lèi)對(duì)象是什么,自然也無(wú)法使用 new 關(guān)鍵字來(lái)創(chuàng)建對(duì)象了。
這時(shí)候,我們使用 JDK 提供的反射 API 進(jìn)行反射調(diào)用:
Class clz = Class.forName("com.chenshuyi.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
上面兩段代碼的執(zhí)行結(jié)果,其實(shí)是完全一樣的。但是其思路完全不一樣,第一段代碼在未運(yùn)行時(shí)就已經(jīng)確定了要運(yùn)行的類(lèi)(Apple),而第二段代碼則是在運(yùn)行時(shí)通過(guò)字符串值才得知要運(yùn)行的類(lèi)(com.chenshuyi.reflect.Apple)。
所以說(shuō)什么是反射?
反射就是在運(yùn)行時(shí)才知道要操作的類(lèi)是什么,并且可以在運(yùn)行時(shí)獲取類(lèi)的完整構(gòu)造,并調(diào)用對(duì)應(yīng)的方法。
一個(gè)簡(jiǎn)單的例子
上面提到的示例程序,其完整的程序代碼如下:
public class Apple {
private int price;
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public static void main(String[] args) throws Exception{
//正常的調(diào)用
Apple apple = new Apple();
apple.setPrice(5);
System.out.println("Apple Price:" + apple.getPrice());
//使用反射調(diào)用
Class clz = Class.forName("com.chenshuyi.api.Apple");
Method setPriceMethod = clz.getMethod("setPrice", int.class);
Constructor appleConstructor = clz.getConstructor();
Object appleObj = appleConstructor.newInstance();
setPriceMethod.invoke(appleObj, 14);
Method getPriceMethod = clz.getMethod("getPrice");
System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
}
}
從代碼中可以看到我們使用反射調(diào)用了 setPrice 方法,并傳遞了 14 的值。之后使用反射調(diào)用了 getPrice 方法,輸出其價(jià)格。上面的代碼整個(gè)的輸出結(jié)果是:
Apple Price:5
Apple Price:14
從這個(gè)簡(jiǎn)單的例子可以看出,一般情況下我們使用反射獲取一個(gè)對(duì)象的步驟:
- 獲取類(lèi)的 Class 對(duì)象實(shí)例
Class clz = Class.forName("com.zhenai.api.Apple");
- 根據(jù) Class 對(duì)象實(shí)例獲取 Constructor 對(duì)象
Constructor appleConstructor = clz.getConstructor();
- 使用 Constructor 對(duì)象的 newInstance 方法獲取反射類(lèi)對(duì)象
Object appleObj = appleConstructor.newInstance();
而如果要調(diào)用某一個(gè)方法,則需要經(jīng)過(guò)下面的步驟:
- 獲取方法的 Method 對(duì)象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
- 利用 invoke 方法調(diào)用方法
setPriceMethod.invoke(appleObj, 14);
到這里,我們已經(jīng)能夠掌握反射的基本使用。但如果要進(jìn)一步掌握反射,還需要對(duì)反射的常用 API 有更深入的理解。
在 JDK 中,反射相關(guān)的 API 可以分為下面幾個(gè)方面:獲取反射的 Class 對(duì)象、通過(guò)反射創(chuàng)建類(lèi)對(duì)象、通過(guò)反射獲取類(lèi)屬性方法及構(gòu)造器。
反射常用API
獲取反射中的Class對(duì)象
在反射中,要獲取一個(gè)類(lèi)或調(diào)用一個(gè)類(lèi)的方法,我們首先需要獲取到該類(lèi)的 Class 對(duì)象。
在 Java API 中,獲取 Class 類(lèi)對(duì)象有三種方法:
第一種,使用 Class.forName 靜態(tài)方法。當(dāng)你知道該類(lèi)的全路徑名時(shí),你可以使用該方法獲取 Class 類(lèi)對(duì)象。
Class clz = Class.forName("java.lang.String");
第二種,使用 .class 方法。
這種方法只適合在編譯前就知道操作的 Class。
Class clz = String.class;
第三種,使用類(lèi)對(duì)象的 getClass() 方法。
String str = new String("Hello");
Class clz = str.getClass();
通過(guò)反射創(chuàng)建類(lèi)對(duì)象
通過(guò)反射創(chuàng)建類(lèi)對(duì)象主要有兩種方式:通過(guò) Class 對(duì)象的 newInstance() 方法、通過(guò) Constructor 對(duì)象的 newInstance() 方法。
第一種:通過(guò) Class 對(duì)象的 newInstance() 方法。
Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();
第二種:通過(guò) Constructor 對(duì)象的 newInstance() 方法
Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();
通過(guò) Constructor 對(duì)象創(chuàng)建類(lèi)對(duì)象可以選擇特定構(gòu)造方法,而通過(guò) Class 對(duì)象則只能使用默認(rèn)的無(wú)參數(shù)構(gòu)造方法。下面的代碼就調(diào)用了一個(gè)有參數(shù)的構(gòu)造方法進(jìn)行了類(lèi)對(duì)象的初始化。
Class clz = Apple.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("紅富士", 15);
通過(guò)反射獲取類(lèi)屬性、方法、構(gòu)造器
我們通過(guò) Class 對(duì)象的 getFields() 方法可以獲取 Class 類(lèi)的屬性,但無(wú)法獲取私有屬性。
Class clz = Apple.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
輸出結(jié)果是:
price
而如果使用 Class 對(duì)象的 getDeclaredFields() 方法則可以獲取包括私有屬性在內(nèi)的所有屬性:
Class clz = Apple.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
輸出結(jié)果是:
name
price
與獲取類(lèi)屬性一樣,當(dāng)我們?nèi)カ@取類(lèi)方法、類(lèi)構(gòu)造器時(shí),如果要獲取私有方法或私有構(gòu)造器,則必須使用有 declared 關(guān)鍵字的方法。
反射源碼解析
當(dāng)我們懂得了如何使用反射后,今天我們就來(lái)看看 JDK 源碼中是如何實(shí)現(xiàn)反射的?;蛟S大家平時(shí)沒(méi)有使用過(guò)反射,但是在開(kāi)發(fā) Web 項(xiàng)目的時(shí)候會(huì)遇到過(guò)下面的異常:
java.lang.NullPointerException
...
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
可以看到異常堆棧指出了異常在 Method 的第 497 的 invoke 方法中,其實(shí)這里指的 invoke 方法就是我們反射調(diào)用方法中的 invoke。
Method method = clz.getMethod("setPrice", int.class);
method.invoke(object, 4); //就是這里的invoke方法
例如我們經(jīng)常使用的 Spring 配置中,經(jīng)常會(huì)有相關(guān) Bean 的配置:
<bean class="com.chenshuyi.Apple">
</bean>
當(dāng)我們?cè)?XML 文件中配置了上面這段配置之后,Spring 便會(huì)在啟動(dòng)的時(shí)候利用反射去加載對(duì)應(yīng)的 Apple 類(lèi)。而當(dāng) Apple 類(lèi)不存在或發(fā)生啟發(fā)異常時(shí),異常堆棧便會(huì)將異常指向調(diào)用的 invoke 方法。
從這里可以看出,我們平常很多框架都使用了反射,而反射中最最終的就是 Method 類(lèi)的 invoke 方法了。
下面我們來(lái)看看 JDK 的 invoke 方法到底做了些什么。
進(jìn)入 Method 的 invoke 方法我們可以看到,一開(kāi)始是進(jìn)行了一些權(quán)限的檢查,最后是調(diào)用了 MethodAccessor 類(lèi)的 invoke 方法進(jìn)行進(jìn)一步處理,如下圖紅色方框所示。

那么 MethodAccessor 又是什么呢?
其實(shí) MethodAccessor 是一個(gè)接口,定義了方法調(diào)用的具體操作,而它有三個(gè)具體的實(shí)現(xiàn)類(lèi):
- sun.reflect.DelegatingMethodAccessorImpl
- sun.reflect.MethodAccessorImpl
- sun.reflect.NativeMethodAccessorImpl
而要看 ma.invoke() 到底調(diào)用的是哪個(gè)類(lèi)的 invoke 方法,則需要看看 MethodAccessor 對(duì)象返回的到底是哪個(gè)類(lèi)對(duì)象,所以我們需要進(jìn)入 acquireMethodAccessor() 方法中看看。

從 acquireMethodAccessor() 方法我們可以看到,代碼先判斷是否存在對(duì)應(yīng)的 MethodAccessor 對(duì)象,如果存在那么就復(fù)用之前的 MethodAccessor 對(duì)象,否則調(diào)用 ReflectionFactory 對(duì)象的 newMethodAccessor 方法生成一個(gè) MethodAccessor 對(duì)象。

在 ReflectionFactory 類(lèi)的 newMethodAccessor 方法里,我們可以看到首先是生成了一個(gè) NativeMethodAccessorImpl 對(duì)象,再這個(gè)對(duì)象作為參數(shù)調(diào)用 DelegatingMethodAccessorImpl 類(lèi)的構(gòu)造方法。
這里的實(shí)現(xiàn)是使用了代理模式,將 NativeMethodAccessorImpl 對(duì)象交給 DelegatingMethodAccessorImpl 對(duì)象代理。我們查看 DelegatingMethodAccessorImpl 類(lèi)的構(gòu)造方法可以知道,其實(shí)是將 NativeMethodAccessorImpl 對(duì)象賦值給 DelegatingMethodAccessorImpl 類(lèi)的 delegate 屬性。

所以說(shuō)ReflectionFactory 類(lèi)的 newMethodAccessor 方法最終返回 DelegatingMethodAccessorImpl 類(lèi)對(duì)象。所以我們?cè)谇懊娴?ma.invoke() 里,其將會(huì)進(jìn)入 DelegatingMethodAccessorImpl 類(lèi)的 invoke 方法中。

進(jìn)入 DelegatingMethodAccessorImpl 類(lèi)的 invoke 方法后,這里調(diào)用了 delegate 屬性的 invoke 方法,它又有兩個(gè)實(shí)現(xiàn)類(lèi),分別是:DelegatingMethodAccessorImpl 和 NativeMethodAccessorImpl。按照我們前面說(shuō)到的,這里的 delegate 其實(shí)是一個(gè) NativeMethodAccessorImpl 對(duì)象,所以這里會(huì)進(jìn)入 NativeMethodAccessorImpl 的 invoke 方法。

而在 NativeMethodAccessorImpl 的 invoke 方法里,其會(huì)判斷調(diào)用次數(shù)是否超過(guò)閥值(numInvocations)。如果超過(guò)該閥值,那么就會(huì)生成另一個(gè)MethodAccessor 對(duì)象,并將原來(lái) DelegatingMethodAccessorImpl 對(duì)象中的 delegate 屬性指向最新的 MethodAccessor 對(duì)象。
到這里,其實(shí)我們可以知道 MethodAccessor 對(duì)象其實(shí)就是具體去生成反射類(lèi)的入口。通過(guò)查看源碼上的注釋?zhuān)覀兛梢粤私獾?MethodAccessor 對(duì)象的一些設(shè)計(jì)信息。
"Inflation" mechanism. Loading bytecodes to implement Method.invoke() and Constructor.newInstance() currently costs 3-4x more than an invocation via native code for the first invocation (though subsequent invocations have been benchmarked to be over 20x faster).Unfortunately this cost increases startup time for certain applications that use reflection intensively (but only once per class) to bootstrap themselves.
Inflation 機(jī)制。初次加載字節(jié)碼實(shí)現(xiàn)反射,使用 Method.invoke() 和 Constructor.newInstance() 加載花費(fèi)的時(shí)間是使用原生代碼加載花費(fèi)時(shí)間的 3 - 4 倍。這使得那些頻繁使用反射的應(yīng)用需要花費(fèi)更長(zhǎng)的啟動(dòng)時(shí)間。
To avoid this penalty we reuse the existing JVM entry points for the first few invocations of Methods and Constructors and then switch to the bytecode-based implementations. Package-private to be accessible to NativeMethodAccessorImpl and NativeConstructorAccessorImpl.
為了避免這種痛苦的加載時(shí)間,我們?cè)诘谝淮渭虞d的時(shí)候重用了 JVM 的入口,之后切換到字節(jié)碼實(shí)現(xiàn)的實(shí)現(xiàn)。
就像注釋里說(shuō)的,實(shí)際的 MethodAccessor 實(shí)現(xiàn)有兩個(gè)版本,一個(gè)是 Native 版本,一個(gè)是 Java 版本。
Native 版本一開(kāi)始啟動(dòng)快,但是隨著運(yùn)行時(shí)間邊長(zhǎng),速度變慢。Java 版本一開(kāi)始加載慢,但是隨著運(yùn)行時(shí)間邊長(zhǎng),速度變快。正是因?yàn)閮煞N存在這些問(wèn)題,所以第一次加載的時(shí)候我們會(huì)發(fā)現(xiàn)使用的是 NativeMethodAccessorImpl 的實(shí)現(xiàn),而當(dāng)反射調(diào)用次數(shù)超過(guò) 15 次之后,則使用 MethodAccessorGenerator 生成的 MethodAccessorImpl 對(duì)象去實(shí)現(xiàn)反射。
Method 類(lèi)的 invoke 方法整個(gè)流程可以表示成如下的時(shí)序圖:

講到這里,我們了解了 Method 類(lèi)的 invoke 方法的具體實(shí)現(xiàn)方式。知道了原來(lái) invoke 方法內(nèi)部有兩種實(shí)現(xiàn)方式,一種是 native 原生的實(shí)現(xiàn)方式,一種是 Java 實(shí)現(xiàn)方式,這兩種各有千秋。而為了最大化性能優(yōu)勢(shì),JDK 源碼使用了代理的設(shè)計(jì)模式去實(shí)現(xiàn)最大化性能。
博主個(gè)人獨(dú)立站點(diǎn)開(kāi)通啦!歡迎點(diǎn)擊訪(fǎng)問(wèn):https://shuyi.tech


浙公網(wǎng)安備 33010602011771號(hào)