面試官:說說反射的底層實現原理?

China Soft發表於2024-03-15

反射是 Java 面試中必問的面試題,但只有很少人能真正的理解“反射”並講明白反射,更別說能說清楚它的底層實現原理了。所以本文就透過大白話的方式來系統的講解一下反射,希望大家看完之後能真正的理解並掌握“反射”這項技術。

1.什麼是反射?

反射在程式執行期間動態獲取類和操縱類的一種技術。透過反射機制,可以在執行時動態地建立物件、呼叫方法、訪問和修改屬性,以及獲取類的資訊。

2.反射的應用有哪些?

反射在日常開發中使用的地方有很多,例如以下幾個:

  1. 動態代理:反射是動態代理的底層實現,即在執行時動態地建立代理物件,並攔截和增強方法呼叫。這常用於實現 AOP 功能,如日誌記錄、事務管理等。
  2. Bean 建立:Spring/Spring Boot 專案中,在專案啟動時,建立的 Bean 物件就是透過反射來實現的。
  3. JDBC 連線:JDBC 中的 DriverManager 類透過反射載入並註冊資料庫驅動,這是 Java 資料庫連線的標準做法。

3.反射實現

反射的關鍵實現方法有以下幾個:

  1. 得到類:Class.forName("類名")
  2. 得到所有欄位:getDeclaredFields()
  3. 得到所有方法:getDeclaredMethods()
  4. 得到構造方法:getDeclaredConstructor()
  5. 得到例項:newInstance()
  6. 呼叫方法:invoke()

具體使用示例如下:

// 1.反射得到物件
Class<?> clazz = Class.forName("User");
// 2.得到方法
Method method = clazz.getDeclaredMethod("publicMethod");
// 3.得到靜態方法
Method staticMethod = clazz.getDeclaredMethod("staticMethod");
// 4.執行靜態方法
staticMethod.invoke(clazz);

反射執行私有方法程式碼實現如下:

// 1.反射得到物件
Class<?> clazz = Class.forName("User");
// 2.得到私有方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
// 3.設定私有方法可訪問
privateMethod.setAccessible(true);
// 4.得到例項
Object user = clazz.getDeclaredConstructor().newInstance();
// 5.執行私有方法
privateMethod.invoke(user);

4.底層實現原理

從上述內容可以看出,對於反射來說,操縱類最主要的方法是 invoke,所以搞懂了 invoke 方法的實現,也就搞定了反射的底層實現原理了。

invoke 方法的執行流程如下:

  1. 查詢方法:當透過 java.lang.reflect.Method 物件呼叫 invoke 方法時,Java 虛擬機器(JVM)首先確認該方法是否存在並可以訪問。這包括檢查方法的訪問許可權、方法簽名是否匹配等。
  2. 安全檢查:如果方法是私有的或受保護的,還需要進行訪問許可權的安全檢查。如果當前呼叫者沒有足夠的許可權訪問這個方法,將丟擲 IllegalAccessException。
  3. 引數轉換和適配:invoke 方法接受一個物件例項和一組引數,需要將這些引數轉換成對應方法簽名所需要的型別,並且進行必要的型別檢查和裝箱拆箱操作。
  4. 方法呼叫:對於非私有方法,Java 反射實際上是透過 JNI(Java Native Interface,Java 本地介面)呼叫到 JVM 內部的 native 方法,例如 java.lang.reflect.Method.invoke0()。這個 native 方法負責完成真正的動態方法呼叫。對於 Java 方法,JVM 會透過方法表、虛方法表(vtable)進行查詢和呼叫;對於非虛方法或者靜態方法,JVM 會直接呼叫相應的方法實現。
  5. 異常處理:在執行方法的過程中,如果出現任何異常,JVM 會捕獲並將異常包裝成 InvocationTargetException 丟擲,應用程式可以透過這個異常獲取到原始異常資訊。
  6. 返回結果:如果方法正常執行完畢,invoke 方法會返回方法的執行結果,或者如果方法返回型別是 void,則不返回任何值。

透過這種方式,Java 反射的 invoke 方法能夠打破編譯時的繫結,實現執行時動態呼叫物件的方法,提供了極大的靈活性,但也帶來了執行時效能損耗和安全隱患(如破壞封裝性、違反訪問控制等)。

5.優缺點分析

反射的優點如下:

  1. 靈活性:使用反射可以在執行時動態載入類,而不需要在編譯時就將類載入到程式中。這對於需要動態擴充套件程式功能的情況非常有用。
  2. 可擴充套件性:使用反射可以使程式更加靈活和可擴充套件,同時也可以提高程式的可維護性和可測試性。
  3. 實現更多功能:許多框架都使用反射來實現自動化配置和依賴注入等功能。例如,Spring 框架就使用反射來實現依賴注入。

反射的缺點如下:

  1. 效能問題:使用反射會帶來一定的效能問題,因為反射需要在執行時動態獲取類的資訊,這比在編譯時就獲取資訊要慢。
  2. 安全問題:使用反射可以訪問和修改類的欄位和方法,這可能會導致安全問題。因此,在使用反射時需要格外小心,確保不會對程式的安全性造成影響。

課後思考

為什麼反射的執行效率比較低?動態代理的實現除了反射之外,還有沒有其他的實現方法?

本文已收錄到我的面試小站 www.javacn.site,其中包含的內容有:Redis、JVM、併發、併發、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、設計模式、訊息佇列等模組。

相關文章