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

Java 反射動態調用未知類指定方法:泛型工具類實作

     編輯:Java 相關知識

框架開發、插件系統、腳本引擎整合時,常需在運行期根據類名、方法名動態調用目標方法。本文封裝一個類型安全、支援可變參數、異常統一轉換的泛型工具類 ReflectionInvoker,替代原始反射的冗長代碼。

核心需求

  • 傳入任意對象實例、方法名、參數陣列,自動匹配重載方法。
  • 支援基本類型自動裝箱/拆箱(int ↔ Integer)。
  • 檢查型別相容,失敗時拋出含完整簽名的 ReflectionException。
  • 快取 Method 對象,避免重複 getMethod 開銷。

工具類完整實作

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final class ReflectionInvoker {

    private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();

    private ReflectionInvoker() {}

    /**
     * 動態調用目標對象的指定方法
     *
     * @param target     目標實例(非 static 方法)或 Class(static 方法)
     * @param methodName 方法名
     * @param args       實際參數值陣列
     * @return 方法返回值(void 返回 null)
     * @throws ReflectionException 找不到方法、參數不匹配、調用拋出異常
     */
    @SuppressWarnings("unchecked")
    public static <T> T invoke(Object target, String methodName, Object... args) {
        Class<?> targetClass = (target instanceof Class) ? (Class<?>) target : target.getClass();
        Class<?>[] paramTypes = toClassArray(args);

        Method method = findMethod(targetClass, methodName, paramTypes);
        if (method == null) {
            throw new ReflectionException("方法不存在: " + targetClass.getName() + "#" + methodName + "(" + Arrays.toString(paramTypes) + ")");
        }

        try {
            method.setAccessible(true);
            Object result = method.invoke(target instanceof Class ? null : target, args);
            return (T) result;
        } catch (IllegalAccessException | InvocationTargetException e) {
            Throwable cause = e instanceof InvocationTargetException ? e.getCause() : e;
            throw new ReflectionException("調用失敗: " + method.toGenericString(), cause);
        }
    }

    private static Method findMethod(Class<?> clazz, String name, Class<?>[] paramTypes) {
        String key = clazz.getName() + "#" + name + "#" + Arrays.toString(paramTypes);
        return METHOD_CACHE.computeIfAbsent(key, k -> {
            // 精確匹配
            try {
                return clazz.getMethod(name, paramTypes);
            } catch (NoSuchMethodException ignored) {}

            // 兼容基本類型/包裝類型自動轉換
            for (Method m : clazz.getMethods()) {
                if (!m.getName().equals(name)) continue;
                Class<?>[] mp = m.getParameterTypes();
                if (mp.length != paramTypes.length) continue;
                boolean match = true;
                for (int i = 0; i < mp.length; i++) {
                    if (!isAssignable(mp[i], paramTypes[i])) { match = false; break; }
                }
                if (match) return m;
            }
            return null;
        });
    }

    private static boolean isAssignable(Class<?> formal, Class<?> actual) {
        if (formal.isPrimitive()) {
            return wrapperToPrimitive(formal) == actual || formal == actual;
        }
        return formal.isAssignableFrom(actual);
    }

    private static Class<?> wrapperToPrimitive(Class<?> wrapper) {
        if (wrapper == Integer.class) return int.class;
        if (wrapper == Long.class) return long.class;
        if (wrapper == Double.class) return double.class;
        if (wrapper == Boolean.class) return boolean.class;
        if (wrapper == Character.class) return char.class;
        if (wrapper == Byte.class) return byte.class;
        if (wrapper == Short.class) return short.class;
        if (wrapper == Float.class) return float.class;
        return wrapper;
    }

    private static Class<?>[] toClassArray(Object... args) {
        if (args == null) return new Class[0];
        Class<?>[] types = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            types[i] = args[i] != null ? args[i].getClass() : Object.class;
        }
        return types;
    }

    public static class ReflectionException extends RuntimeException {
        public ReflectionException(String msg) { super(msg); }
        public ReflectionException(String msg, Throwable cause) { super(msg, cause); }
    }
}

使用範例

// 1. 實例方法
UserService service = new UserServiceImpl();
User user = ReflectionInvoker.invoke(service, "findById", 1001L);

// 2. 靜態方法
String json = ReflectionInvoker.invoke(JsonUtil.class, "toJson", user);

// 3. 多態參數自動匹配
Number num = ReflectionInvoker.invoke(Calculator.class, "add", 1, 2L); // int + long -> Number

// 4. 異常處理
try {
    ReflectionInvoker.invoke(bean, "riskyMethod");
} catch (ReflectionInvoker.ReflectionException e) {
    log.error("反射調用異常", e);
}

效能優化建議

  1. 快取命中率: 同一簽名多次調用僅首次反射查找,後續直接從 ConcurrentHashMap 取得 Method。
  2. setAccessible(true): 繞過存取檢查,JIT 可內聯優化。
  3. 參數型別精確: 傳入參數盡量使用確切型別(如 Long.valueOf(id) 而非 id 自動推導為 Integer),減少匹配輪詢。
  4. 高頻熱點: 考慮改用 MethodHandle (Java 7+) 或 LambdaMetafactory (Java 8+) 生成直接呼叫點,效能接近直接調用。

安全性提醒

  • 僅對受信代碼開放,避免任意方法調用導致安全漏洞(如 RCE)。
  • 生產環境建議加入白名單檢查:`if (!ALLOWED_METHODS.contains(key)) throw ...`。
  • 模組化系統(JPMS/Java 9+)需確保目標模組 `opens` 相關包給調用模組。

封裝健壯的反射調用器,能讓動態特性代碼保持簡潔、可測、可維護,同時兼顧效能與安全。

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