Java之反射的使用解析
一、反射的基本概念:什么是反射?為什么需要反射?
1.1 反射的定義
反射(Reflection)是 Java 語言提供的一種核心機制,它允許程序在運行時(而非編譯期)實現(xiàn)以下功能:
獲取類的完整結(jié)構(gòu)信息
- 可以獲取任意類的類名、修飾符、包信息等元數(shù)據(jù)
- 能查詢類的繼承關(guān)系,包括父類和實現(xiàn)的接口
- 可以獲取類的所有字段(Field)信息,包括字段名、類型、修飾符等
- 能獲取類的所有方法(Method)信息,包括方法名、參數(shù)類型、返回類型等
- 可以獲取類的構(gòu)造器(Constructor)信息
動態(tài)操作類實例
- 通過Class對象的newInstance()方法或Constructor對象動態(tài)創(chuàng)建實例
- 可以繞過訪問限制,調(diào)用類的私有方法(通過Method.setAccessible(true))
- 能修改類的私有字段值(通過Field.setAccessible(true))
- 可以動態(tài)處理注解信息,獲取運行時注解配置
反射的核心類
- java.lang.Class:表示正在運行的Java類和接口
- java.lang.reflect.Field:表示類的字段
- java.lang.reflect.Method:表示類的方法
- java.lang.reflect.Constructor:表示類的構(gòu)造方法
反射機制使Java程序具備了"自省"能力,允許程序在運行時檢查和修改自身狀態(tài),是Java實現(xiàn)動態(tài)特性的關(guān)鍵技術(shù)。
例如,在Spring框架中,通過反射可以動態(tài)創(chuàng)建Bean實例并注入依賴,而不需要硬編碼。
1.2 為什么需要反射?
靜態(tài)方式 vs 反射方式
在編譯期已知類的結(jié)構(gòu)時,通常使用靜態(tài)方式:
// 靜態(tài)方式創(chuàng)建對象
User user = new User();
// 靜態(tài)方式調(diào)用方法
user.setName("張三");
但在以下場景必須使用反射:
典型應(yīng)用場景
框架開發(fā)
- Spring框架的IoC容器:通過反射動態(tài)創(chuàng)建Bean實例并注入依賴
- MyBatis的SQL映射:通過反射將查詢結(jié)果映射到Java對象
- JUnit測試框架:通過反射發(fā)現(xiàn)并執(zhí)行測試方法
動態(tài)代理
- JDK動態(tài)代理:通過反射調(diào)用被代理對象的方法
- Spring AOP:利用反射實現(xiàn)方法攔截和增強
配置化開發(fā)
# config.properties className=com.example.UserService methodName=getUserInfo
通過反射根據(jù)配置文件動態(tài)加載類和調(diào)用方法
開發(fā)工具
- IDE的代碼提示和自動補全功能
- 調(diào)試工具的變量查看功能
- 對象序列化工具(如Jackson)通過反射訪問對象屬性
特殊需求場景
- 調(diào)用私有方法進行單元測試
- 動態(tài)修改final字段的值(通過反射可以繞過final限制)
- 實現(xiàn)插件化架構(gòu),動態(tài)加載第三方類
反射的優(yōu)勢
- 靈活性:可以編寫高度通用的代碼,處理未知類型的對象
- 擴展性:支持運行時動態(tài)加載和操作類
- 解耦性:減少代碼間的直接依賴,提高可維護性
例如,一個通用的對象屬性拷貝工具:
public static void copyProperties(Object source, Object target) {
// 通過反射獲取所有字段
Field[] fields = source.getClass().getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
Object value = field.get(source);
Field targetField = target.getClass().getDeclaredField(field.getName());
targetField.setAccessible(true);
targetField.set(target, value);
} catch (Exception e) {
// 處理異常
}
}
}
二、反射的核心基礎(chǔ):Class 類
反射是 Java 語言的一種強大特性,它允許程序在運行時動態(tài)地獲取類信息并操作對象。反射的所有操作都圍繞 java.lang.Class 類展開,它是反射機制的"入口"和核心。
2.1 Class 類的深入特性
Class 類作為反射的基礎(chǔ),具有以下重要特性:
- 繼承關(guān)系:
Class類本身是一個具體的 Java 類,繼承自Object類,位于java.lang包中。 - 全局唯一性:在 JVM 中,每個 Java 類(包括普通類、接口、枚舉、數(shù)組、注解、基本數(shù)據(jù)類型甚至是 void 類型)在加載后,都會生成一個對應(yīng)的且唯一的
Class對象。 - 單例模式:無論一個類創(chuàng)建了多少個實例對象,其對應(yīng)的
Class對象在 JVM 中始終只有一個。例如,String類可能有無數(shù)個實例,但String.class對應(yīng)的Class對象只有一個。 - 自動創(chuàng)建:
Class對象由 JVM 在類加載時自動創(chuàng)建,程序員不能通過new關(guān)鍵字直接創(chuàng)建Class對象。 - 類型標識:
Class對象包含了類的完整結(jié)構(gòu)信息,包括字段、方法、構(gòu)造器、父類、接口、注解等元數(shù)據(jù)。
2.2 獲取 Class 對象的三種核心方式詳解
方式 1:通過對象的getClass()方法
適用場景:當已經(jīng)存在某個類的實例對象時,可以直接通過該對象獲取其 Class 對象。
import com.example.User;
public class ReflectionDemo {
public static void main(String[] args) {
// 先創(chuàng)建對象實例
User user = new User("張三", 25);
// 通過對象實例調(diào)用getClass()方法
Class<?> clazz = user.getClass();
// 輸出類信息
System.out.println("全類名: " + clazz.getName()); // 輸出: com.example.User
System.out.println("簡單類名: " + clazz.getSimpleName()); // 輸出: User
System.out.println("是否是接口: " + clazz.isInterface()); // 輸出: false
}
}
特點:
- 需要先創(chuàng)建對象實例
- 適用于運行時動態(tài)獲取對象類型
- 不會觸發(fā)類的靜態(tài)初始化塊
方式 2:通過 "類名.class" 語法
適用場景:在編譯期已知類名的情況下,直接通過類名加 .class 獲取 Class 對象。
import com.example.User;
public class ReflectionDemo {
public static void main(String[] args) {
// 直接通過類名.class獲取Class對象
Class<User> clazz = User.class;
// 輸出類信息
System.out.println("規(guī)范類名: " + clazz.getCanonicalName());
System.out.println("修飾符: " + java.lang.reflect.Modifier.toString(clazz.getModifiers()));
// 檢查是否是數(shù)組類
System.out.println("是否是數(shù)組: " + clazz.isArray()); // 輸出: false
// 獲取父類
Class<?> superClass = clazz.getSuperclass();
System.out.println("父類: " + (superClass != null ? superClass.getName() : "無"));
}
}
特點:
- 編譯期即可確定類型,最直接高效
- 不需要創(chuàng)建對象實例
- 不會觸發(fā)類的靜態(tài)初始化塊
- 支持基本數(shù)據(jù)類型的 Class 對象獲?。ㄈ?
int.class)
方式 3:通過Class.forName()方法動態(tài)加載
適用場景:當類名在編譯期不確定,需要通過字符串形式(全類名)動態(tài)加載類時使用。
public class ReflectionDemo {
public static void main(String[] args) {
try {
// 動態(tài)加載類并獲取Class對象
String className = "com.example.User"; // 可從配置文件讀取
Class<?> clazz = Class.forName(className);
// 輸出類信息
System.out.println("類加載器: " + clazz.getClassLoader());
System.out.println("是否是注解: " + clazz.isAnnotation());
System.out.println("是否是枚舉: " + clazz.isEnum());
// 獲取包信息
Package pkg = clazz.getPackage();
System.out.println("包名: " + (pkg != null ? pkg.getName() : "默認包"));
} catch (ClassNotFoundException e) {
// 處理類未找到異常
System.err.println("類加載失敗,原因: " + e.getMessage());
e.printStackTrace();
}
}
}
特點:
- 類名以字符串形式傳入,靈活性高
- 會觸發(fā)類的靜態(tài)初始化塊(執(zhí)行類中的
static{}代碼塊) - 需要處理
ClassNotFoundException異常 - 常用于框架開發(fā)(如 Spring 的組件掃描)
- 可以指定類加載器(重載方法:
Class.forName(String name, boolean initialize, ClassLoader loader))
2.3 Class 類的常用方法詳解
類名相關(guān)方法
// 獲取不同類型的類名
Class<?> clazz = User.class;
System.out.println("getName(): " + clazz.getName()); // com.example.User
System.out.println("getSimpleName(): " + clazz.getSimpleName()); // User
System.out.println("getCanonicalName(): " + clazz.getCanonicalName()); // com.example.User
System.out.println("getTypeName(): " + clazz.getTypeName()); // com.example.User
// 數(shù)組類型示例
Class<?> arrayClazz = String[].class;
System.out.println("數(shù)組類名: " + arrayClazz.getName()); // [Ljava.lang.String;
繼承關(guān)系方法
// 獲取父類和接口
Class<?> superClass = clazz.getSuperclass();
System.out.println("父類: " + (superClass != null ? superClass.getName() : "無"));
Class<?>[] interfaces = clazz.getInterfaces();
System.out.println("實現(xiàn)的接口:");
for (Class<?> iface : interfaces) {
System.out.println(" - " + iface.getName());
}
// 檢查繼承關(guān)系
System.out.println("是否是Object的子類: " + Object.class.isAssignableFrom(clazz));
System.out.println("是否是User的父類: " + clazz.isAssignableFrom(User.class));
修飾符和包信息
// 解析類修飾符
int modifiers = clazz.getModifiers();
System.out.println("修飾符: " + Modifier.toString(modifiers));
System.out.println("是否是public: " + Modifier.isPublic(modifiers));
System.out.println("是否是final: " + Modifier.isFinal(modifiers));
System.out.println("是否是abstract: " + Modifier.isAbstract(modifiers));
// 獲取包信息
Package pkg = clazz.getPackage();
System.out.println("包名: " + pkg.getName());
System.out.println("包注解: " + Arrays.toString(pkg.getAnnotations()));
注解處理方法
// 獲取類上的注解
Annotation[] annotations = clazz.getAnnotations();
System.out.println("類注解數(shù)量: " + annotations.length);
for (Annotation ann : annotations) {
System.out.println(" - " + ann.annotationType().getName());
}
// 獲取特定類型的注解
Service serviceAnno = clazz.getAnnotation(Service.class);
if (serviceAnno != null) {
System.out.println("Service注解值: " + serviceAnno.value());
}
// 檢查注解存在性
System.out.println("是否有@Component注解: " + clazz.isAnnotationPresent(Component.class));
成員變量方法
// 獲取所有public字段(包括繼承的)
Field[] fields = clazz.getFields();
System.out.println("Public字段數(shù)量: " + fields.length);
// 獲取所有字段(不包括繼承的)
Field[] declaredFields = clazz.getDeclaredFields();
System.out.println("所有字段數(shù)量: " + declaredFields.length);
for (Field field : declaredFields) {
System.out.println(" - " + Modifier.toString(field.getModifiers()) + " " +
field.getType().getSimpleName() + " " + field.getName());
}
// 獲取特定字段
try {
Field nameField = clazz.getDeclaredField("name");
System.out.println("找到name字段: " + nameField);
} catch (NoSuchFieldException e) {
System.err.println("未找到指定字段");
}
方法相關(guān)操作
// 獲取所有public方法(包括繼承的)
Method[] methods = clazz.getMethods();
System.out.println("Public方法數(shù)量: " + methods.length);
// 獲取所有方法(不包括繼承的)
Method[] declaredMethods = clazz.getDeclaredMethods();
System.out.println("所有方法數(shù)量: " + declaredMethods.length);
for (Method method : declaredMethods) {
System.out.println(" - " + method.getName() + " 參數(shù)數(shù): " + method.getParameterCount());
}
// 獲取特定方法
try {
Method setNameMethod = clazz.getMethod("setName", String.class);
System.out.println("找到setName(String)方法: " + setNameMethod);
} catch (NoSuchMethodException e) {
System.err.println("未找到指定方法");
}
構(gòu)造器操作
// 獲取所有public構(gòu)造器
Constructor<?>[] constructors = clazz.getConstructors();
System.out.println("Public構(gòu)造器數(shù)量: " + constructors.length);
// 獲取所有構(gòu)造器
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
System.out.println("所有構(gòu)造器數(shù)量: " + declaredConstructors.length);
for (Constructor<?> ctor : declaredConstructors) {
System.out.println(" - 參數(shù)數(shù): " + ctor.getParameterCount());
}
// 獲取特定構(gòu)造器
try {
Constructor<?> ctor = clazz.getConstructor(String.class, int.class);
System.out.println("找到User(String, int)構(gòu)造器: " + ctor);
// 使用構(gòu)造器創(chuàng)建實例
Object userInstance = ctor.newInstance("李四", 30);
System.out.println("創(chuàng)建的實例: " + userInstance);
} catch (Exception e) {
System.err.println("構(gòu)造器操作失敗: " + e.getMessage());
}
類型檢查方法
// 類型檢查
System.out.println("是否是基本類型: " + clazz.isPrimitive()); // false
System.out.println("是否是數(shù)組: " + clazz.isArray()); // false
System.out.println("是否是接口: " + clazz.isInterface()); // false
System.out.println("是否是枚舉: " + clazz.isEnum()); // false
System.out.println("是否是注解: " + clazz.isAnnotation()); // false
System.out.println("是否是本地類: " + clazz.isLocalClass()); // false
System.out.println("是否是匿名類: " + clazz.isAnonymousClass()); // false
System.out.println("是否是成員類: " + clazz.isMemberClass()); // false
System.out.println("是否是合成類: " + clazz.isSynthetic()); // false
// 數(shù)組類型檢查
Class<?> arrayClass = String[].class;
System.out.println("String[]是數(shù)組: " + arrayClass.isArray());
System.out.println("數(shù)組組件類型: " + arrayClass.getComponentType().getName());
資源訪問方法
// 獲取類資源
URL resource = clazz.getResource("/application.properties");
System.out.println("資源URL: " + resource);
InputStream inputStream = clazz.getResourceAsStream("/config.json");
if (inputStream != null) {
System.out.println("找到資源流");
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 獲取類所在目錄
String classLocation = clazz.getProtectionDomain().getCodeSource().getLocation().getPath();
System.out.println("類所在位置: " + classLocation);
三、反射的核心操作:動態(tài)操作類的成員
掌握Class對象后,即可通過反射動態(tài)操作類的字段、方法和構(gòu)造器。以下以com.example.User類為例(定義如下),演示核心操作:
package com.example;
public class User {
// 字段(公開、私有、靜態(tài))
public String username;
private Integer age;
public static String school;
// 構(gòu)造器(無參、有參)
public User() {
System.out.println("無參構(gòu)造器執(zhí)行");
}
private User(String username, Integer age) {
this.username = username;
this.age = age;
System.out.println("私有有參構(gòu)造器執(zhí)行:" + username + "," + age);
}
// 方法(公開、私有、靜態(tài))
public void sayHello() {
System.out.println("Hello, " + username);
}
private String getAgeInfo(Integer minAge) {
return username + "的年齡是" + age + ",是否成年:" + (age >= minAge);
}
public static void printSchool() {
System.out.println("學(xué)校:" + school);
}
// Getter和Setter(省略toString())
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
這個User類包含:
- 三種可見性的字段:public username、private age和public static school
- 兩個構(gòu)造器:默認無參構(gòu)造器和私有有參構(gòu)造器
- 多種方法:public實例方法sayHello、private實例方法getAgeInfo、public static方法printSchool
- 標準的getter/setter方法
通過反射可以:
- 獲取類的所有構(gòu)造器,包括私有構(gòu)造器
- 訪問和修改所有字段值,包括私有字段
- 調(diào)用所有方法,包括私有方法
- 操作靜態(tài)字段和方法
- 繞過訪問控制檢查(通過setAccessible方法)
典型應(yīng)用場景包括:
- 框架開發(fā)(如Spring的依賴注入)
- 動態(tài)代理
- 對象序列化/反序列化
- 單元測試中訪問私有成員
- 插件系統(tǒng)實現(xiàn)
注意事項:
- 反射操作會降低性能
- 破壞封裝性,應(yīng)謹慎使用
- 需要處理各種異常(NoSuchMethodException等)
3.1 動態(tài)操作字段(Field)
通過反射機制,我們可以使用Class對象獲取Field對象,進而實現(xiàn)對字段的動態(tài)讀取和修改操作,包括訪問私有字段。這種技術(shù)常用于框架開發(fā)、測試工具、序列化/反序列化等場景。
核心步驟詳解
獲取Class對象:
- 通過
Class.forName("全限定類名")加載類 - 通過
類名.class獲取 - 通過對象實例的
getClass()方法獲取
獲取Field對象:
getField(String name):僅能獲取public字段getDeclaredField(String name):可獲取所有聲明的字段(包括private/protected)getFields():獲取所有public字段(包括繼承的)getDeclaredFields():獲取類中聲明的所有字段
訪問控制處理:
- 對于非public字段,必須調(diào)用
setAccessible(true)解除訪問限制 - 這可能會引發(fā)SecurityException,需適當處理
字段操作:
讀取:field.get(Object obj)
- 實例字段:傳入對象實例
- 靜態(tài)字段:傳入null
修改:field.set(Object obj, Object value)
- 注意類型匹配
- 可配合
field.getType()進行類型檢查
完整的實戰(zhàn)示例
import com.example.User;
import java.lang.reflect.Field;
public class FieldReflectionDemo {
public static void main(String[] args) throws Exception {
// 1. 獲取User類的Class對象(三種方式示例)
Class<?> userClass = User.class; // 方式1:類名.class
// Class<?> userClass = Class.forName("com.example.User"); // 方式2:全限定類名
// Class<?> userClass = new User().getClass(); // 方式3:對象實例.getClass()
// 2. 操作公開實例字段:username
// 2.1 獲取username字段的Field對象(只能是public字段)
Field usernameField = userClass.getField("username");
System.out.println("username字段類型:" + usernameField.getType().getName());
// 2.2 創(chuàng)建User實例(相當于 new User())
User user = (User) userClass.getDeclaredConstructor().newInstance();
// 2.3 修改username值
usernameField.set(user, "張三");
// 2.4 讀取username值
String username = (String) usernameField.get(user);
System.out.println("公開字段username:" + username); // 輸出:張三
// 3. 操作私有實例字段:age(關(guān)鍵:setAccessible(true))
// 3.1 獲取age字段的Field對象(使用getDeclaredField獲取私有字段)
Field ageField = userClass.getDeclaredField("age");
System.out.println("age字段修飾符:" + Modifier.toString(ageField.getModifiers()));
// 3.2 取消訪問檢查(否則訪問私有字段會拋出IllegalAccessException)
ageField.setAccessible(true);
// 3.3 修改age值(自動裝箱處理)
ageField.set(user, 25);
// 3.4 讀取age值
Integer age = (Integer) ageField.get(user);
System.out.println("私有字段age:" + age); // 輸出:25
// 4. 操作靜態(tài)公開字段:school(靜態(tài)字段無需實例,傳null)
Field schoolField = userClass.getField("school");
// 4.1 修改靜態(tài)字段值
schoolField.set(null, "北京大學(xué)"); // 靜態(tài)字段obj參數(shù)傳null
// 4.2 讀取靜態(tài)字段值
String school = (String) schoolField.get(null);
System.out.println("靜態(tài)字段school:" + school); // 輸出:北京大學(xué)
// 5. 批量獲取所有字段示例
System.out.println("\nUser類所有字段:");
Field[] fields = userClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true); // 解除所有字段的訪問限制
System.out.println(field.getName() + ": " + field.get(user));
}
}
}
應(yīng)用場景說明
框架開發(fā):
- Spring框架的依賴注入
- ORM框架的字段映射(如Hibernate)
測試工具:
- 單元測試中訪問私有字段進行驗證
- Mock工具修改final字段
序列化/反序列化:
- JSON/XML轉(zhuǎn)換工具
- 深度拷貝實現(xiàn)
動態(tài)配置:
- 運行時根據(jù)配置修改對象狀態(tài)
- 熱更新字段值
注意事項
- 性能考慮:反射操作比直接訪問慢,應(yīng)避免在性能敏感場景頻繁使用
- 安全限制:某些安全管理器可能禁止修改訪問控制
- 類型安全:運行時類型檢查,可能引發(fā)ClassCastException
- 兼容性:字段名變更會導(dǎo)致反射代碼失效
3.2 動態(tài)調(diào)用方法(Method)
通過反射機制,我們可以動態(tài)調(diào)用任意類的方法,包括私有方法、靜態(tài)方法甚至是父類方法。這種能力在框架開發(fā)、單元測試和AOP編程中非常有用。
核心步驟詳解
1.獲取Class對象:
- 通過Class.forName("全限定類名")
- 通過對象.getClass()
- 通過類名.class
2.獲取Method對象:
- getMethod():獲取公共方法(包括繼承的)
- getDeclaredMethod():獲取本類聲明的所有方法(包括私有)
- 需要指定方法名和參數(shù)類型數(shù)組(無參傳空數(shù)組)
3.訪問控制處理:
- 對于非public方法,必須調(diào)用setAccessible(true)
- 會破壞封裝性,需謹慎使用
- 可以通過SecurityManager限制此操作
4.方法調(diào)用:
- invoke(Object obj, Object... args)
- 實例方法:第一個參數(shù)為對象實例
- 靜態(tài)方法:第一個參數(shù)傳null
- 可變參數(shù):會自動裝箱/拆箱
- 會拋出InvocationTargetException(封裝被調(diào)用方法拋出的異常)
5.返回值處理:
- 有返回值:需要強制類型轉(zhuǎn)換
- 無返回值:返回null
- 基本類型:會自動裝箱
實戰(zhàn)示例擴展
?
import com.example.User; import java.lang.reflect.*;
public class MethodReflectionDemo {
public static void main(String[] args) throws Exception {
// 1. 獲取Class對象的三種方式演示
Class<?> userClass1 = Class.forName("com.example.User");
Class<?> userClass2 = new User().getClass();
Class<?> userClass3 = User.class;
// 2. 實例化對象(推薦使用getDeclaredConstructor().newInstance())
User user = (User) userClass1.getDeclaredConstructor().newInstance();
// 3. 演示帶繼承關(guān)系的方法調(diào)用
Class<?> parentClass = userClass1.getSuperclass();
Method parentMethod = parentClass.getMethod("parentMethod");
parentMethod.invoke(user); // 調(diào)用父類方法
// 4. 方法重載處理示例
try {
// 獲取重載方法時需要精確匹配參數(shù)類型
Method overloadMethod = userClass1.getMethod("setInfo", String.class, int.class);
overloadMethod.invoke(user, "張三", 25);
// 演示基本類型參數(shù)的自動處理
Method intMethod = userClass1.getMethod("processNumber", int.class);
Integer result = (Integer) intMethod.invoke(user, 100);
} catch (NoSuchMethodException e) {
System.out.println("未找到匹配的方法");
}
// 5. 異常處理最佳實踐
try {
Method errorMethod = userClass1.getMethod("throwException");
errorMethod.invoke(user);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
System.out.println("捕獲到方法拋出的異常:" + cause.getMessage());
}
// 6. 性能優(yōu)化建議
// 緩存Method對象避免重復(fù)查找
Method cachedMethod = userClass1.getMethod("toString");
for(int i=0; i<100; i++){
cachedMethod.invoke(user);
}
}
}
?應(yīng)用場景說明
- JUnit測試框架:通過反射調(diào)用測試方法
- Spring框架:依賴注入時調(diào)用setter方法
- ORM框架:動態(tài)調(diào)用實體類的getter/setter
- 動態(tài)代理:方法攔截和處理
- 插件系統(tǒng):動態(tài)加載和執(zhí)行插件方法
注意事項
- 方法查找區(qū)分大小寫
- 參數(shù)類型要完全匹配(包括包裝類型)
- invoke()會拋出被調(diào)用方法的異常
- 反射調(diào)用比直接調(diào)用慢約50-100倍
- 在模塊化系統(tǒng)中可能需要額外配置開放反射權(quán)限
3.3 動態(tài)創(chuàng)建對象(Constructor)
通過Class對象獲取Constructor對象后,可動態(tài)創(chuàng)建類的實例(包括通過私有構(gòu)造器創(chuàng)建)。這種機制在框架開發(fā)、依賴注入、動態(tài)代理等場景中非常有用。
核心步驟詳解
獲取Class對象:
- 可通過Class.forName()、對象.getClass()或直接使用類名.class三種方式獲取
- 例如:Class<?> clazz = Class.forName("com.example.User")
獲取Constructor對象:
- getConstructor(Class<?>... parameterTypes):獲取指定參數(shù)類型的公開構(gòu)造器
- getDeclaredConstructor(Class<?>... parameterTypes):獲取所有可見性修飾的構(gòu)造器(包括private)
- 參數(shù)類型數(shù)組需與構(gòu)造器參數(shù)嚴格匹配,例如構(gòu)造器為User(String name)則需傳String.class
訪問控制處理:
- 對于非公開構(gòu)造器,必須調(diào)用setAccessible(true)方法
- 該方法會取消Java的訪問權(quán)限檢查,但會觸發(fā)SecurityManager檢查
實例化對象:
- newInstance(Object... args)方法接受可變參數(shù)
- 無參構(gòu)造器傳空數(shù)組或null均可
- 構(gòu)造器執(zhí)行可能拋出InstantiationException(抽象類)、IllegalArgumentException(參數(shù)不匹配)等異常
注意事項對比
| 方法特性 | Class.newInstance() | Constructor.newInstance() |
|---|---|---|
| 參數(shù)支持 | 僅無參 | 支持任意參數(shù) |
| 構(gòu)造器可見性 | 必須public | 可訪問private |
| 異常處理 | 包裹異常 | 直接拋出構(gòu)造器異常 |
| JDK版本兼容 | Java 9已過時 | 推薦使用 |
實戰(zhàn)示例加強版
import com.example.User;
import java.lang.reflect.Constructor;
public class ConstructorReflectionDemo {
public static void main(String[] args) {
try {
// 1. 獲取Class對象(三種方式示例)
Class<?> userClass1 = User.class;
Class<?> userClass2 = Class.forName("com.example.User");
Class<?> userClass3 = new User().getClass();
// 2. 公開構(gòu)造器調(diào)用(帶參數(shù)版本)
Constructor<?> publicConstructor = userClass1.getConstructor(String.class);
User userWithName = (User) publicConstructor.newInstance("張三");
// 3. 私有構(gòu)造器調(diào)用(帶異常處理)
try {
Constructor<?> privateConstructor = userClass1.getDeclaredConstructor(int.class);
privateConstructor.setAccessible(true);
User secretUser = (User) privateConstructor.newInstance(100);
} catch (SecurityException e) {
System.err.println("安全管理器禁止訪問私有構(gòu)造器");
}
// 4. 構(gòu)造器參數(shù)自動裝箱處理示例
Constructor<?> boxConstructor = userClass1.getDeclaredConstructor(Integer.class);
boxConstructor.setAccessible(true);
boxConstructor.newInstance(128); // 自動裝箱int->Integer
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}
典型應(yīng)用場景
- Spring框架的Bean實例化
- ORM框架的結(jié)果集映射
- 單元測試中Mock對象的創(chuàng)建
- 反序列化時替代readObject()方法
- 實現(xiàn)工廠模式時消除if-else判斷鏈
性能優(yōu)化建議
- 緩存頻繁使用的Constructor對象
- 對于需要反復(fù)創(chuàng)建的實例,考慮使用Objenesis庫繞過構(gòu)造器調(diào)用
- 在Android開發(fā)中注意ProGuard可能重命名構(gòu)造器的問題
四、反射的應(yīng)用場景:實戰(zhàn)中的典型用法
4.1 場景 1:簡單的 IOC 容器(模擬 Spring)
Spring 的 IOC 容器核心是 "通過配置動態(tài)創(chuàng)建對象",以下用反射實現(xiàn)一個極簡版 IOC:
需求分析
通過 application.properties 配置文件定義類名,容器啟動時自動加載這些類并創(chuàng)建實例。這種方式實現(xiàn)了最基本的依賴注入功能,類似于 Spring 的核心容器功能。
詳細實現(xiàn)步驟
1. 配置文件準備
創(chuàng)建 application.properties 文件,內(nèi)容格式為 key=全限定類名:
user=com.example.User product=com.example.Product
2. 容器核心實現(xiàn)
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class MiniIOC {
// 存儲Bean實例(key:配置中的key,value:反射創(chuàng)建的實例)
private Map<String, Object> beanMap = new HashMap<>();
// 初始化IOC容器:加載配置文件,反射創(chuàng)建實例
public MiniIOC(String configPath) throws Exception {
// 1. 加載配置文件
Properties properties = new Properties();
InputStream inputStream = MiniIOC.class.getClassLoader().getResourceAsStream(configPath);
if (inputStream == null) {
throw new IOException("配置文件不存在:" + configPath);
}
properties.load(inputStream);
// 2. 遍歷配置,反射創(chuàng)建實例
for (String key : properties.stringPropertyNames()) {
String className = properties.getProperty(key);
// 反射加載類并創(chuàng)建實例
Class<?> clazz = Class.forName(className);
Object bean = clazz.getDeclaredConstructor().newInstance();
// 存入BeanMap
beanMap.put(key, bean);
}
}
// 獲取Bean實例
public Object getBean(String key) {
return beanMap.get(key);
}
// 測試
public static void main(String[] args) throws Exception {
MiniIOC ioc = new MiniIOC("application.properties");
User user = (User) ioc.getBean("user");
System.out.println("IOC容器創(chuàng)建的User實例:" + user);
}
}
3. 測試用例
假設(shè) User 類如下:
package com.example;
public class User {
public User() {
System.out.println("User無參構(gòu)造器執(zhí)行");
}
@Override
public String toString() {
return "User實例";
}
}
運行結(jié)果:
User無參構(gòu)造器執(zhí)行
IOC容器創(chuàng)建的User實例:User實例
擴展說明
- 當前實現(xiàn)僅支持無參構(gòu)造器創(chuàng)建實例
- 可以擴展支持帶參數(shù)的構(gòu)造器
- 可以添加依賴注入功能
- 可以增加單例/多例模式支持
4.2 場景 2:動態(tài)代理(模擬 Spring AOP)
需求分析
在不修改目標類代碼的情況下,為目標方法添加日志功能(方法執(zhí)行前后打印日志),模擬 Spring AOP 的核心功能。
詳細實現(xiàn)步驟
1. 定義接口
interface UserService {
void sayHello();
}
2. 實現(xiàn)目標類
class User implements UserService {
public String username;
@Override
public void sayHello() {
System.out.println("Hello, " + username);
}
}
3. 日志代理處理器
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
class LogHandler implements InvocationHandler {
// 目標對象(被代理的對象)
private Object target;
public LogHandler(Object target) {
this.target = target;
}
// 代理方法:所有代理對象的方法調(diào)用都會觸發(fā)此方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置日志
System.out.println("【日志】方法" + method.getName() + "開始執(zhí)行,參數(shù):" + (args == null ? "無" : args[0]));
// 反射調(diào)用目標方法
Object result = method.invoke(target, args);
// 后置日志
System.out.println("【日志】方法" + method.getName() + "執(zhí)行結(jié)束,返回值:" + result);
return result;
}
}
4. 測試代碼
import java.lang.reflect.Proxy;
public class ProxyDemo {
public static void main(String[] args) {
// 1. 創(chuàng)建目標對象
User user = new User();
user.username = "趙六";
// 2. 創(chuàng)建代理對象(JDK動態(tài)代理僅支持接口)
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(), // 類加載器
new Class[]{UserService.class}, // 目標對象實現(xiàn)的接口
new LogHandler(user) // 代理處理器
);
// 3. 調(diào)用代理對象的方法
proxy.sayHello();
}
}
5. 輸出結(jié)果
【日志】方法sayHello開始執(zhí)行,參數(shù):無
Hello, 趙六
【日志】方法sayHello執(zhí)行結(jié)束,返回值:null
擴展說明
- JDK 動態(tài)代理要求目標類必須實現(xiàn)接口
- 可以擴展支持 CGLIB 代理,解決無接口的情況
- 可以擴展支持多個切面(日志、事務(wù)、權(quán)限等)
- 可以增加切點表達式,實現(xiàn)更靈活的代理規(guī)則
五、反射的注意事項:避坑指南
5.1 性能問題:反射比直接調(diào)用慢,需優(yōu)化
深層次性能損耗分析: 反射操作涉及多個層面的性能開銷:
- JVM內(nèi)部需要解析完整的類元數(shù)據(jù),包括繼承關(guān)系、接口實現(xiàn)等
- 安全檢查機制(如訪問權(quán)限驗證)會在每次調(diào)用時執(zhí)行
- 方法調(diào)用涉及參數(shù)類型轉(zhuǎn)換和返回值的包裝
具體性能數(shù)據(jù)對比:
- 簡單方法調(diào)用:反射比直接調(diào)用慢約50-100倍
- 字段訪問:反射比直接訪問慢約20-50倍
- 構(gòu)造函數(shù)調(diào)用:反射比直接構(gòu)造慢約10-30倍
詳細優(yōu)化方案:
對象緩存策略:
將Class對象存儲在靜態(tài)final變量中
private static final Class<?> TARGET_CLASS = TargetClass.class;
對頻繁使用的Method/Field建立緩存Map
private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();?
安全檢查優(yōu)化:
Field field = clazz.getDeclaredField("privateField");
field.setAccessible(true); // 僅第一次需要
// 后續(xù)直接使用field,無需再次setAccessible
熱點代碼替換:
// 不推薦:在循環(huán)中使用反射
for(int i=0; i<10000; i++){
method.invoke(target, args);
}
// 推薦:改為直接調(diào)用
MethodHandle handle = MethodHandles.lookup().unreflect(method);
for(int i=0; i<10000; i++){
handle.invoke(target, args);
}
第三方庫優(yōu)化實踐:
- Apache Commons BeanUtils:適用于簡單屬性操作
- Spring ReflectionUtils:提供了安全的反射封裝
- Byte Buddy/Javassist:支持字節(jié)碼層面的反射優(yōu)化
5.2 安全問題:打破訪問權(quán)限,可能引發(fā)風(fēng)險
安全威脅場景分析:
敏感數(shù)據(jù)泄露:
Field passwordField = user.getClass().getDeclaredField("password");
passwordField.setAccessible(true);
String password = (String) passwordField.get(user);
權(quán)限繞過:
Field adminField = user.getClass().getDeclaredField("isAdmin");
adminField.setAccessible(true);
adminField.set(user, true);
單例破壞:
Constructor<?> constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton anotherInstance = (Singleton) constructor.newInstance();
安全防護措施:
模塊系統(tǒng)配置(Java 9+):
module com.example {
// 僅向特定模塊開放反射權(quán)限
opens com.example.model to spring.core;
}
安全管理器配置:
SecurityManager manager = new SecurityManager() {
@Override
public void checkPermission(Permission perm) {
if (perm instanceof ReflectPermission) {
throw new SecurityException("Reflection not allowed");
}
}
};
System.setSecurityManager(manager);
防御性編程:
public class SecureClass {
static {
// 檢查調(diào)用棧,防止非法反射
if (!isCalledByTrustedCode()) {
throw new IllegalAccessError("Illegal reflection access");
}
}
}
5.3 代碼可讀性與可維護性
典型代碼異味示例:
// 不良實踐:硬編碼字符串
Object result = obj.getClass()
.getMethod("processData", String.class)
.invoke(obj, "input");
改進方案實施步驟:
常量定義:
public interface ReflectionConstants {
String PROCESS_METHOD = "processData";
Class<?>[] PROCESS_PARAMS = {String.class};
}
工具類封裝:
public class ReflectionUtils {
public static Object safeInvoke(Object target, String methodName,
Class<?>[] paramTypes, Object... args) {
try {
Method method = target.getClass().getMethod(methodName, paramTypes);
return method.invoke(target, args);
} catch (Exception e) {
throw new ReflectionException("Invocation failed", e);
}
}
}
文檔規(guī)范:
/** * 通過反射調(diào)用目標方法 * @param target 目標對象 * @param methodName 方法名(需與ReflectionConstants中定義一致) * @param args 參數(shù)列表 * @return 方法執(zhí)行結(jié)果 * @throws ReflectionException 當反射操作失敗時拋出 */ public static Object reflectiveCall(Object target, String methodName, Object... args)
類型安全檢查:
if (!method.getReturnType().isAssignableFrom(expectedReturnType)) {
throw new IllegalArgumentException("Return type mismatch");
}
5.4 兼容性問題
典型兼容性問題案例:
字段重命名:
// 舊代碼
field = clazz.getDeclaredField("userName");
// 類重構(gòu)后
private String loginName; // 原userName被重命名
方法簽名變更:
// 舊反射調(diào)用
method = clazz.getMethod("save", User.class);
// 方法改為
public void save(User user, boolean async); // 參數(shù)列表變更
系統(tǒng)化解決方案:
版本兼容策略:
try {
field = clazz.getDeclaredField("userName");
} catch (NoSuchFieldException e) {
// 兼容舊版本
field = clazz.getDeclaredField("loginName");
}
注解標記系統(tǒng):
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface ReflectMapping {
String[] alternateNames() default {};
}
// 使用示例
@ReflectMapping(alternateNames = {"userName"})
private String loginName;
自動化測試方案:
@Test
public void testReflectionCompatibility() {
ReflectionTestUtils.assertFieldExists(TargetClass.class, "userName");
ReflectionTestUtils.assertMethodExists(TargetClass.class, "save", User.class);
}
5.5 其他注意事項
詳細技術(shù)要點:
靜態(tài)成員處理:
// 正確方式
Field staticField = clazz.getDeclaredField("STATIC_VALUE");
staticField.set(null, newValue); // obj參數(shù)為null
Method staticMethod = clazz.getMethod("staticMethod");
staticMethod.invoke(null); // obj參數(shù)為null
基本類型處理:
// 獲取基本類型字段
Field intField = clazz.getDeclaredField("count");
if (intField.getType() == int.class) {
int value = intField.getInt(target); // 使用專門的方法
}
// 處理自動裝箱
Object boxedValue = 42; // 自動裝箱為Integer
if (field.getType().isPrimitive()) {
// 需要手動拆箱
int value = ((Number)boxedValue).intValue();
}
泛型類型解析:
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType)genericType).getActualTypeArguments();
Class<?> actualClass = (Class<?>) actualTypes[0];
}
跨版本兼容方案:
// Java 8及以下
sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe();
// Java 9+
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
} catch (Exception e) {
// 備選方案
}
動態(tài)代理集成:
public class DebugProxy implements InvocationHandler {
private Object target;
public static Object newInstance(Object obj) {
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new DebugProxy(obj));
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置處理
Object result = method.invoke(target, args);
// 后置處理
return result;
}
}
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java使用Math.random()結(jié)合蒙特卡洛方法計算pi值示例
這篇文章主要介紹了Java使用Math.random()結(jié)合蒙特卡洛方法計算pi值的方法,簡單說明了結(jié)合具體實例蒙特卡洛方法的原理,并結(jié)合具體實例形式分析了java使用蒙特卡洛方法計算PI值的操作技巧,需要的朋友可以參考下2017-09-09
thymeleaf中前后端數(shù)據(jù)交互方法匯總
這篇文章主要介紹了thymeleaf中前后端數(shù)據(jù)交互小結(jié),本文通過示例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2022-07-07
深入解析Java中的編碼轉(zhuǎn)換以及編碼和解碼操作
這篇文章主要介紹了Java中的編碼轉(zhuǎn)換以及編碼和解碼操作,文中詳細解讀了編碼解碼的相關(guān)IO操作以及內(nèi)存使用方面的知識,需要的朋友可以參考下2016-02-02
如何使用CountDownLatch同步j(luò)ava多線程
這篇文章主要介紹了如何使用CountDownLatch同步j(luò)ava多線程,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08
淺談Java中常用數(shù)據(jù)結(jié)構(gòu)的實現(xiàn)類 Collection和Map
下面小編就為大家?guī)硪黄獪\談Java中常用數(shù)據(jù)結(jié)構(gòu)的實現(xiàn)類 Collection和Map。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09
Springboot+hibernate實現(xiàn)簡單的增刪改查示例
今天小編就為大家分享一篇Springboot+hibernate實現(xiàn)簡單的增刪改查示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08

