文章目錄
- 引言
- 反射的基本概念
- 反射基本原理
- 反射應用場景
- 反射基本使用
- 獲取類的Class對象
- 獲取構造方法并實例化對象
- 獲取和調用方法
- 獲取和修改字段
- 反射工具類
- 反射源碼解讀
- 獲取Class對象的源碼
- 調用方法的源碼
- 反射優缺點
- 優點
- 缺點
- 為什么需要反射
- 總結
引言
Java反射是Java語言中的一種動態機制,它允許在運行時檢查和操作類的結構和行為。反射的強大功能使得程序可以在運行時動態加載類、調用方法和訪問字段,從而極大地增強了Java程序的靈活性和擴展性。
反射的基本概念
反射(Reflection)是指程序在運行時能夠自我檢查和操作自身的能力。通過反射,可以獲取類的構造器、方法、字段等信息,并能動態調用對象的方法、設置或獲取對象的字段值。
反射關鍵信息
Class
: 代表類的實體,在運行時加載類時會創建對應的Class對象。Constructor
: 代表類的構造方法。Method
: 代表類的方法。Field
: 代表類的字段。
Java反射
最核心的類位于JDK源碼 java.lang.reflect
包下,比如Class、Constructor、Field 和 Method
等,他們提供了對類和對象運行時信息進行檢查和操作的方法。
反射基本原理
Java反射的核心在于Class
類,它包含了關于類的所有信息。在Java虛擬機(JVM
)加載類時,會為每個類創建一個對應的Class
對象,該對象保存了類的元數據。通過這些元數據,程序可以在運行時獲取類的詳細信息并進行操作。
主要可以從下面 4個點來闡述:
- 類加載:當 Java程序運行時,類加載器會根據類的名稱查找并加載類的字節碼文件,然后將字節碼文件轉換為可執行的 Java類,并將其存儲在運行時數據區域的方法區中。
- 創建 Class對象:在類加載過程中,Java虛擬機會自動創建對應的
Class對象
,Class對象包含了類的元數據信息,并提供了訪問和操作類的接口。 - 獲取 Class對象:Class對象通過多種方式獲取,最常見的方式有 3種: 類的 .class屬性、類實例的 getClass()方法、Class.forName()。
- 訪問和操作:通過
Class對象
獲取類的字段、方法、構造函數等信息,使用Field
類和Method
類來訪問和操作字段和方法,甚至可以調用私有的字段和方法。
通過上述的分析可以看出:反射機制需要基于Java虛擬機
對類的加載、存儲和訪問機制的支持,通過反射,可以在運行時動態地探索和操作類的信息,實現靈活的編程和代碼的動態行為。
反射應用場景
很多優秀的框架內部都使用了Java反射
,這里重點講解下給 Java打下半壁江山的 Spring生態(Spring Framework,Spring MVC,SpringBoot, SpringCloud…),以 Spring Framework為例:
- 依賴注入(Dependency Injection) : 依賴注入,可以把程序員主動創建對象的事情交給 Spring管理,大大提升了對象創建的靈活性。當我們在配置文件或用注解定義 Bean時,Spring會使用反射來動態地實例化對象,并將依賴的其他對象注入到這些實例中。
- 自動裝配(Autowired) : 當 Spring容器啟動時,它會掃描應用程序中的所有類,并使用反射來查找和識別帶有 @Autowired注解的字段、方法或構造函數。再自動將 Bean注入到需要的位置,實現對象之間的自動連接。
- AOP(Aspect-Oriented Programming) : AOP 利用了動態代理和反射機制。通過定義切面(Aspect)和切點(Pointcut),Spring可以在運行時使用反射來創建代理對象,從而實現橫切關注點(cross-cutting concerns)的功能,如日志記錄、事務管理等。
- 動態代理(Dynamic Proxy) : Spring利用 Java反射機制動態地創建代理對象,并在代理對象中添加額外的邏輯,從而實現對目標對象的增強。
- 框架擴展和定制: Spring通過反射機制來實現對應用程序的擴展和定制的。例如,Spring提供了BeanPostProcessor接口,允許開發人員在 Bean初始化前后插入自定義邏輯,這是通過反射來實現的。
另外,還有一些耳熟能詳的框架也使用了Java反射
:
- JUnit:JUnit是一個優秀的單元測試框架,它利用了 Java反射機制動態地加載和執行測試方法。
- Jackson:Jackson是一個 JSON處理的 Java庫,它利用反射來實現 JSON與 Java對象之間的轉換,動態讀取和寫入 Java對象的屬性,并將其轉換為 JSON格式。
- Hibernate ORM:Hibernate和 MyBatis一樣,都是對象關系映射框架,通過反射來實現對象與數據庫表之間的映射關系。
總結以下幾點:
- 框架設計: 許多Java框架(如Spring、Hibernate)廣泛使用反射來實現依賴注入、面向切面編程等功能。
- 調試和測試: 反射允許動態訪問和修改對象,方便調試和測試私有方法和字段。
- 動態代理: 通過反射實現動態代理,增強程序的靈活性和可擴展性。
- 類瀏覽器和可視化工具: 反射幫助開發工具展示類的結構和關系。
反射基本使用
獲取類的Class對象
Class<?> clazz = Class.forName("com.example.MyClass");
// 或者
Class<?> clazz = MyClass.class;
// 或者
Class<?> clazz = myObject.getClass();
獲取構造方法并實例化對象
Constructor<?> constructor = clazz.getConstructor(String.class);
Object instance = constructor.newInstance("example");
獲取和調用方法
Method method = clazz.getMethod("myMethod", String.class);
method.invoke(instance, "Hello");
獲取和修改字段
Field field = clazz.getDeclaredField("myField");
// 允許訪問私有字段
field.setAccessible(true);
field.set(instance, "New Value");
String value = (String) field.get(instance);
反射工具類
@Slf4j
public class ReflectionUtil {/*** 獲取屬性名以及對應的屬性值** @param o 對象* @return map*/public static Map<String, Object> getFieldNameAndValue(Object o) {Map<String, Object> resMap = new LinkedHashMap<>();Class<?> clazz = o.getClass();while (Objects.nonNull(clazz)) {Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {String fieldName = field.getName();if (Objects.isNull(resMap.get(fieldName))) {resMap.put(fieldName, getFieldValueByName(fieldName, o));}}clazz = clazz.getSuperclass();}return resMap;}/*** 獲取屬性名以及對應的屬性值** @param list 對象數組* @return list\<map\>*/public static List<Map<String, Object>> getFieldNameAndValueMaps(List<?> list) {List<Map<String, Object>> resMaps = new ArrayList<>(list.size());for (Object o : list) {if (Objects.isNull(o)) {throw new RuntimeException("Arrays Cannot Contain Null... ...");}Map<String, Object> fieldNameAndValueMap = getFieldNameAndValue(o);resMaps.add(fieldNameAndValueMap);}return resMaps;}public static List<String> getFieldNames(Class<?> clazz) {List<String> fieldList = new ArrayList<>();while (Objects.nonNull(clazz)) {Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {String fieldName = field.getName();fieldList.add(fieldName);}clazz = clazz.getSuperclass();}return fieldList;}/*** 獲取類上的指定字段。如果在類本身上找不到該字段,則將遞歸檢查超類。** @param clazz source class* @param fieldName 字段名* @return 字段*/private static Field getField(Class<?> clazz, String fieldName) {try {return clazz.getDeclaredField(fieldName);} catch (NoSuchFieldException nsf) {if (clazz.getSuperclass() != null) {return getField(clazz.getSuperclass(), fieldName);}throw new IllegalStateException("Could not locate field '" + fieldName + "' on class " + clazz);}}/*** 根據屬性名獲取屬性值** @param fieldName 屬性名稱* @param o 對象* @return Object*/public static Object getFieldValueByName(String fieldName, Object o) {try {String firstLetter = fieldName.substring(0, 1).toUpperCase();String getter = "get" + firstLetter + fieldName.substring(1);Method method = o.getClass().getMethod(getter);return method.invoke(o);} catch (Exception e) {log.info("根據屬性名獲取屬性值異常:" + e.getMessage() + "\n" + e);return null;}}@SuppressWarnings({"all"})public static void setFiledValue(Object bean, String filedName, Object value) throws NoSuchFieldException, IllegalAccessException {Class<?> aClass = bean.getClass();Field field = getField(aClass, filedName);field.setAccessible(true);field.set(bean, value);}
}
反射源碼解讀
反射的實現依賴于JVM提供的本地方法接口(JNI),通過調用本地方法實現對類信息的獲取和操作。
獲取Class對象的源碼
public static Class<?> forName(String className) throws ClassNotFoundException {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();if (classLoader == null) {classLoader = ClassLoader.getSystemClassLoader();}return classLoader.loadClass(className);
}
調用方法的源碼
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, obj, modifiers);}}MethodAccessor ma = methodAccessor; // read volatileif (ma == null) {ma = acquireMethodAccessor();}return ma.invoke(obj, args);
}
從源碼可以看出:Method.invoke()
方法,真實返回的是接口MethodAccessor.invoke()
方法。MethodAccessor
接口有三個實現類,具體是調用哪個類的 invoke 方法?
跟到源碼最后可以發現:Method.invoke()
方法最終調用 native的invoke0()
,應用層面的操作最終轉換成對操作系統 c/c++方法的調用。
反射優缺點
優點
- 靈活性: 反射允許在運行時動態操作類,提高了程序的靈活性和擴展性。
- 動態代理: 通過反射可以實現動態代理機制,廣泛應用于AOP(面向切面編程)等領域。
- 通用性: 反射可以用來編寫通用的框架和庫,增強代碼的重用性。
缺點
- 性能開銷: 反射操作較為耗時,可能會影響程序性能。
- 安全問題: 反射可以繞過訪問控制,修改私有字段和方法,可能引發安全問題。
- 代碼復雜性: 使用反射可能增加代碼的復雜性和維護難度。
為什么需要反射
反射機制
在 Java中的作用不言而喻,下面列舉了反射機制的一些常見場景和原因:
- 運行時類型檢查:反射機制允許在運行時獲取類的信息,包括字段、方法和構造方法等。因此,在進行運行時類型檢查,以確保代碼在處理不同類型的對象時能夠正確地進行操作。
- 動態創建對象:通過反射,可以在運行時動態地創建對象,而不需要在編譯時知道具體的類名。這對于某些需要根據條件或配置來創建對象的情況非常有用,例如工廠模式或依賴注入框架。
- 訪問和修改私有成員:反射機制可以繞過訪問權限限制,訪問和修改類的私有字段和方法。雖然這破壞了封裝性原則,但在某些特定情況下,這種能力可以幫助我們進行一些特殊操作,例如單元測試、調試或框架的內部實現。
- 動態調用方法:反射機制允許我們在運行時動態地調用類的方法,甚至可以根據運行時的條件來選擇不同的方法。這對于實現插件化系統、處理回調函數或實現動態代理等功能非常有用。
- 框架和庫的實現:許多Java框架和庫在其實現中廣泛使用了反射機制。它們利用反射來自動發現和加載類、實現依賴注入、處理注解、配置文件解析和動態代理等。反射機制使得這些框架和庫更加靈活和擴展。
總結
Java反射是一個強大的工具,極大地增強了Java語言的動態性和靈活性。然而,在使用反射時需要權衡其性能開銷和安全風險。Java反射
有優點也有缺點,從整體上看,Java反射
是以犧牲了小部分的性能換取了更好的擴展性和靈活性,犧牲小我成就大我
,而且,隨著現代硬件設備能力越來越強,這點小性能的犧牲是完全值得的。理解反射的原理和使用場景,可以更好地應用反射技術來解決實際開發中的問題。