首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 反射機制深度解析與 Method.invoke 實戰詳解

     編輯:Java 相關知識

反射是 Java 動態性的基石,框架(Spring、Hibernate、MyBatis)的核心實現均建立於此。本文系統梳理反射 API 核心用法,重點剖析 Method.invoke 參數語義與常見誤區,附帶可直接復用的工具方法。

一、反射核心能力

  1. 運行期判斷任意對象所屬類
  2. 運行期構造任意類的實例
  3. 運行期獲取類的成員變量與方法
  4. 運行期調用對象的任意方法
  5. 生成動態代理

二、核心 API 速查

類/介面核心作用
Class<T>類的運行期元數據入口
Field成員變量操作(讀/寫、修飾符、泛型類型)
Method方法操作(調用、參數/返回類型、異常、註解)
Constructor<T>構造器操作(新建實例、參數類型、註解)
Array數組動態創建與訪問

三、六大經典操作模式

1. 獲取實例字段值

public Object getProperty(Object owner, String fieldName) throws Exception {
    Class<?> ownerClass = owner.getClass();
    Field field = ownerClass.getField(fieldName);  // 僅 public 字段
    // Field field = ownerClass.getDeclaredField(fieldName); // 含私有,需 setAccessible(true)
    return field.get(owner);
}

關鍵點:getField 只能獲取 public 字段(含繼承);getDeclaredField 獲取本類聲明的所有字段(含私有),需呼叫 field.setAccessible(true) 破壞封裝。

2. 獲取靜態字段值

public Object getStaticProperty(String className, String fieldName) throws Exception {
    Class<?> ownerClass = Class.forName(className);
    Field field = ownerClass.getField(fieldName);
    return field.get(null);  // 靜態字段傳 null 或 Class 對象均可
}

3. 調用實例方法

public Object invokeMethod(Object owner, String methodName, Object[] args) throws Exception {
    Class<?> ownerClass = owner.getClass();
    Class<?>[] argsClass = new Class[args.length];
    for (int i = 0; i < args.length; i++) {
        argsClass[i] = args[i].getClass();  // 注意:基本類型參數會得到包裝類,可能導致匹配失敗
    }
    Method method = ownerClass.getMethod(methodName, argsClass);
    return method.invoke(owner, args);
}

Method.invoke 參數詳解:

  • owner:方法接收者(實例對象),靜態方法傳 null。
  • args:可變參數陣列,對應方法形參列表;基本類型需傳包裝類實例,陣列參數需傳陣列對象(非展開)。
  • 返回值:方法返回值(基本類型自動裝箱),void 方法返回 null。
  • 異常:拋出 IllegalAccessException(無權限)、InvocationTargetException(目標方法拋出異常,真實異常在 getCause() 中)。

4. 調用靜態方法

public Object invokeStaticMethod(String className, String methodName, Object[] args) throws Exception {
    Class<?> ownerClass = Class.forName(className);
    Class<?>[] argsClass = new Class[args.length];
    for (int i = 0; i < args.length; i++) {
        argsClass[i] = args[i].getClass();
    }
    Method method = ownerClass.getMethod(methodName, argsClass);
    return method.invoke(null, args);  // 第一參數傳 null
}

5. 動態創建實例(帶參構造器)

public Object newInstance(String className, Object[] args) throws Exception {
    Class<?> clazz = Class.forName(className);
    Class<?>[] argsClass = new Class[args.length];
    for (int i = 0; i < args.length; i++) {
        argsClass[i] = args[i].getClass();
    }
    Constructor<?> cons = clazz.getConstructor(argsClass);
    return cons.newInstance(args);
}

// 無參構造器簡化版
public Object newInstanceSimple(String className) throws Exception {
    return Class.forName(className).getDeclaredConstructor().newInstance();
}

6. 判斷實例類型 & 數組操作

// instanceof 替代品
public boolean isInstance(Object obj, Class<?> cls) {
    return cls.isInstance(obj);
}

// 動態獲取數組元素
public Object getArrayElement(Object array, int index) {
    return Array.get(array, index);
}

四、Method.invoke 進階避坑指南

1. 參數類型匹配陷阱

// 錯誤示範:基本類型參數
Method m = clazz.getMethod("setAge", int.class);  // 正確:用 int.class
// Method m = clazz.getMethod("setAge", Integer.class); // 錯誤:找不到方法

// 調用時
m.invoke(obj, 25);  // 自動裝箱為 Integer,內部會拆箱匹配 int

規則:getMethod 參數類型必須與方法聲明完全一致(基本類型用 int.class、陣列用 String[].class);invoke 實參允許裝箱/拆箱與多態賦值。

2. 私有方法訪問

Method m = clazz.getDeclaredMethod("internalCalc", int.class);
m.setAccessible(true);  // 關鍵:破壞封裝
Object result = m.invoke(target, 100);

3. 異常處理最佳實踐

try {
    return method.invoke(target, args);
} catch (InvocationTargetException e) {
    Throwable cause = e.getCause();  // 獲取真實業務異常
    if (cause instanceof RuntimeException) throw (RuntimeException) cause;
    if (cause instanceof Exception) throw (Exception) cause;
    throw new RuntimeException(cause);
} catch (IllegalAccessException e) {
    throw new IllegalStateException("Reflection access denied", e);
}

4. 性能優化建議

  • 緩存 Method/Field/Constructor 對象:反射解析較慢,建議靜態緩存或使用 MethodHandle(Java 7+)。
  • setAccessible(true) 僅設置一次:重複調用無額外開銷,但首次調用會觸發安全檢查。
  • 高頻調用場景改用 Lambda/Metafactory:Java 8+ MethodHandles.Lookup 生成的調用點接近直接調用性能。

五、實戰場景:通用 Bean 拷貝工具

public static void copyProperties(Object source, Object target) {
    Class<?> srcClass = source.getClass();
    Class<?> tgtClass = target.getClass();
    for (Field srcField : srcClass.getDeclaredFields()) {
        srcField.setAccessible(true);
        try {
            Field tgtField = tgtClass.getDeclaredField(srcField.getName());
            tgtField.setAccessible(true);
            if (tgtField.getType().isAssignableFrom(srcField.getType())) {
                tgtField.set(target, srcField.get(source));
            }
        } catch (NoSuchFieldException ignored) {
            // 目標無對應字段,忽略
        }
    }
}

反射賦予了 Java 動態組裝、掃描、代理的能力,但「大權在手,責任在肩」。生產代碼中請遵循:最小權限原則(僅開放必要訪問)、緩存元數據對象、妥善處理檢查型異常、關鍵路徑考慮 MethodHandle 替代。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved