面試官:說說反射的底層實(shí)現(xiàn)原理?
反射是 Java 面試中必問的面試題,但只有很少人能真正的理解“反射”并講明白反射,更別說能說清楚它的底層實(shí)現(xiàn)原理了。所以本文就通過大白話的方式來系統(tǒng)的講解一下反射,希望大家看完之后能真正的理解并掌握“反射”這項(xiàng)技術(shù)。
1.什么是反射?
反射在程序運(yùn)行期間動(dòng)態(tài)獲取類和操縱類的一種技術(shù)。
2.反射的應(yīng)用有哪些?
反射在日常開發(fā)中使用的地方有很多,例如以下幾個(gè):
- 動(dòng)態(tài)代理:反射是動(dòng)態(tài)代理的底層實(shí)現(xiàn),即在運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建代理對(duì)象,并攔截和增強(qiáng)方法調(diào)用。這常用于實(shí)現(xiàn) AOP 功能,如日志記錄、事務(wù)管理等。
- Bean 創(chuàng)建:Spring/Spring Boot 項(xiàng)目中,在項(xiàng)目啟動(dòng)時(shí),創(chuàng)建的 Bean 對(duì)象就是通過反射來實(shí)現(xiàn)的。
- JDBC 連接:JDBC 中的 DriverManager 類通過反射加載并注冊(cè)數(shù)據(jù)庫驅(qū)動(dòng),這是 Java 數(shù)據(jù)庫連接的標(biāo)準(zhǔn)做法。
3.反射實(shí)現(xiàn)
反射的關(guān)鍵實(shí)現(xiàn)方法有以下幾個(gè):
- 得到類:Class.forName("類名")
- 得到所有字段:getDeclaredFields()
- 得到所有方法:getDeclaredMethods()
- 得到構(gòu)造方法:getDeclaredConstructor()
- 得到實(shí)例:newInstance()
- 調(diào)用方法:invoke()
具體使用示例如下:
// 1.反射得到對(duì)象
Class<?> clazz = Class.forName("User");
// 2.得到方法
Method method = clazz.getDeclaredMethod("publicMethod");
// 3.得到靜態(tài)方法
Method staticMethod = clazz.getDeclaredMethod("staticMethod");
// 4.執(zhí)行靜態(tài)方法
staticMethod.invoke(clazz);
反射執(zhí)行私有方法代碼實(shí)現(xiàn)如下:
// 1.反射得到對(duì)象
Class<?> clazz = Class.forName("User");
// 2.得到私有方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
// 3.設(shè)置私有方法可訪問
privateMethod.setAccessible(true);
// 4.得到實(shí)例
Object user = clazz.getDeclaredConstructor().newInstance();
// 5.執(zhí)行私有方法
privateMethod.invoke(user);
4.底層實(shí)現(xiàn)原理
從上述內(nèi)容可以看出,對(duì)于反射來說,操縱類最主要的方法是 invoke,所以搞懂了 invoke 方法的實(shí)現(xiàn),也就搞定了反射的底層實(shí)現(xiàn)原理了。
invoke 方法的執(zhí)行流程如下:
- 查找方法:當(dāng)通過 java.lang.reflect.Method 對(duì)象調(diào)用 invoke 方法時(shí),Java 虛擬機(jī)(JVM)首先確認(rèn)該方法是否存在并可以訪問。這包括檢查方法的訪問權(quán)限、方法簽名是否匹配等。
- 安全檢查:如果方法是私有的或受保護(hù)的,還需要進(jìn)行訪問權(quán)限的安全檢查。如果當(dāng)前調(diào)用者沒有足夠的權(quán)限訪問這個(gè)方法,將拋出 IllegalAccessException。
- 參數(shù)轉(zhuǎn)換和適配:invoke 方法接受一個(gè)對(duì)象實(shí)例和一組參數(shù),需要將這些參數(shù)轉(zhuǎn)換成對(duì)應(yīng)方法簽名所需要的類型,并且進(jìn)行必要的類型檢查和裝箱拆箱操作。
- 方法調(diào)用:對(duì)于非私有方法,Java 反射實(shí)際上是通過 JNI(Java Native Interface,Java 本地接口)調(diào)用到 JVM 內(nèi)部的 native 方法,例如 java.lang.reflect.Method.invoke0()。這個(gè) native 方法負(fù)責(zé)完成真正的動(dòng)態(tài)方法調(diào)用。對(duì)于 Java 方法,JVM 會(huì)通過方法表、虛方法表(vtable)進(jìn)行查找和調(diào)用;對(duì)于非虛方法或者靜態(tài)方法,JVM 會(huì)直接調(diào)用相應(yīng)的方法實(shí)現(xiàn)。
- 異常處理:在執(zhí)行方法的過程中,如果出現(xiàn)任何異常,JVM 會(huì)捕獲并將異常包裝成 InvocationTargetException 拋出,應(yīng)用程序可以通過這個(gè)異常獲取到原始異常信息。
- 返回結(jié)果:如果方法正常執(zhí)行完畢,invoke 方法會(huì)返回方法的執(zhí)行結(jié)果,或者如果方法返回類型是 void,則不返回任何值。
通過這種方式,Java 反射的 invoke 方法能夠打破編譯時(shí)的綁定,實(shí)現(xiàn)運(yùn)行時(shí)動(dòng)態(tài)調(diào)用對(duì)象的方法,提供了極大的靈活性,但也帶來了運(yùn)行時(shí)性能損耗和安全隱患(如破壞封裝性、違反訪問控制等)。
5.優(yōu)缺點(diǎn)分析
反射的優(yōu)點(diǎn)如下:
- 靈活性:使用反射可以在運(yùn)行時(shí)動(dòng)態(tài)加載類,而不需要在編譯時(shí)就將類加載到程序中。這對(duì)于需要?jiǎng)討B(tài)擴(kuò)展程序功能的情況非常有用。
- 可擴(kuò)展性:使用反射可以使程序更加靈活和可擴(kuò)展,同時(shí)也可以提高程序的可維護(hù)性和可測(cè)試性。
- 實(shí)現(xiàn)更多功能:許多框架都使用反射來實(shí)現(xiàn)自動(dòng)化配置和依賴注入等功能。例如,Spring 框架就使用反射來實(shí)現(xiàn)依賴注入。
反射的缺點(diǎn)如下:
- 性能問題:使用反射會(huì)帶來一定的性能問題,因?yàn)榉瓷湫枰谶\(yùn)行時(shí)動(dòng)態(tài)獲取類的信息,這比在編譯時(shí)就獲取信息要慢。
- 安全問題:使用反射可以訪問和修改類的字段和方法,這可能會(huì)導(dǎo)致安全問題。因此,在使用反射時(shí)需要格外小心,確保不會(huì)對(duì)程序的安全性造成影響。