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

Java 反射動態調用:基於方法名/屬性名的靈活取值與執行實戰

     編輯:Java 相關知識

業務痛點:動態字段排序與序列化

場景:用戶自定義列表顯示列與順序(字段名存於數據庫),前端傳遞 JSON 數組 ["name", "age", "email"],後端需按此順序從對象提取值並生成 JSON。若用 if-else 或 switch 硬編碼,維護成本極高;反射提供了優雅的動態解決方案。

方案一:方法名列表 + Method.invoke

public class DynamicGetter {

    public static List<Object> getValuesByMethodNames(Object target, List<String> methodNames) 
            throws ReflectiveOperationException {
        List<Object> results = new ArrayList<>();
        Class<?> clazz = target.getClass();
        
        for (String methodName : methodNames) {
            // 獲取無參方法,拋出 NoSuchMethodException 若不存在
            Method method = clazz.getMethod(methodName); 
            Object value = method.invoke(target); // 實例方法傳 target,靜態方法傳 null
            results.add(value);
        }
        return results;
    }
}

// 使用示例
Person person = new Person("張三", 25, "北京");
List<String> fields = Arrays.asList("getName", "getAge", "getAddress");
List<Object> values = DynamicGetter.getValuesByMethodNames(person, fields);
// values: ["張三", 25, "北京"]

優點:直觀、性能尚可(Method 對象可緩存)。
缺點:要求調用方傳遞完整方法名(含 get 前綴),耦合度較高。

方案二:屬性名 + Introspector (JavaBeans 標準)

利用 java.beans.Introspector 解析屬性描述符,自動映射 propertyName -> getter,更符合 JavaBean 規範。

public class PropertyAccessor {

    private static final Map<Class<?>, BeanInfo> BEAN_INFO_CACHE = new ConcurrentHashMap<>();

    public static Object getPropertyValue(Object target, String propertyName) 
            throws IntrospectionException, ReflectiveOperationException {
        
        BeanInfo beanInfo = BEAN_INFO_CACHE.computeIfAbsent(target.getClass(), 
                clazz -> {
                    try {
                        return Introspector.getBeanInfo(clazz, Object.class);
                    } catch (IntrospectionException e) {
                        throw new IllegalStateException(e);
                    }
                });

        for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
            if (pd.getName().equals(propertyName)) {
                Method getter = pd.getReadMethod();
                if (getter == null) throw new IllegalStateException("不可讀屬性: " + propertyName);
                return getter.invoke(target);
            }
        }
        throw new IllegalArgumentException("屬性不存在: " + propertyName);
    }

    // 批量獲取,返回 LinkedHashMap 保持順序
    public static Map<String, Object> getPropertyValues(Object target, List<String> propertyNames) 
            throws IntrospectionException, ReflectiveOperationException {
        Map<String, Object> result = new LinkedHashMap<>();
        for (String name : propertyNames) {
            result.put(name, getPropertyValue(target, name));
        }
        return result;
    }
}

方案三:高性能緩存 + MethodHandle (JDK 7+)

反射調用有性能開銷(訪問檢查、參數裝箱)。MethodHandle 通過 invokedynamic 指令,經 JIT 優化後性能接近直接調用。

public class MethodHandleAccessor {

    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
    // 緩存: Class -> (propertyName -> MethodHandle)
    private static final ConcurrentMap<Class<?>, Map<String, MethodHandle>> HANDLE_CACHE = new ConcurrentHashMap<>();

    public static Object getValue(Object target, String propertyName) throws Throwable {
        Map<String, MethodHandle> handles = HANDLE_CACHE.computeIfAbsent(target.getClass(), 
                clazz -> {
                    Map<String, MethodHandle> map = new HashMap<>();
                    try {
                        BeanInfo info = Introspector.getBeanInfo(clazz, Object.class);
                        for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
                            Method getter = pd.getReadMethod();
                            if (getter != null) {
                                MethodHandle mh = LOOKUP.unreflect(getter);
                                map.put(pd.getName(), mh);
                            }
                        }
                    } catch (Exception e) {
                        throw new IllegalStateException("MethodHandle 初始化失敗", e);
                    }
                    return map;
                });

        MethodHandle handle = handles.get(propertyName);
        if (handle == null) throw new IllegalArgumentException("屬性不存在: " + propertyName);
        return handle.invokeExact(target); // 精確類型調用,無裝箱
    }
}

性能基準參考 (JMH, 10M 次調用)

方式平均耗時 (ns/op)相對直接調用
直接調用 person.getName()~1.21.0x (基準)
MethodHandle (緩存)~2.5~2.1x
Method.invoke (緩存 Method)~15~12.5x
Method.invoke (每次 getMethod)~120~100x

結論:高頻熱點路徑**必須緩存 Method/MethodHandle**;極致性能場景選 MethodHandle;一般業務 Method.invoke + 緩存足矣。

安全與健壯性指南

  1. 權限檢查:method.setAccessible(true) 破壞封裝,僅在受信代碼(框架/工具類)中使用,業務代碼慎用。
  2. 異常處理:捕獲 NoSuchMethodException, IllegalAccessException, InvocationTargetException,轉為業務異常拋出。
  3. 線程安全:Method、MethodHandle 本身線程安全,緩存用 ConcurrentHashMap 或雙重檢查鎖。
  4. 模塊化限制 (JDK 9+):非導出包的私有成員需在 module-info.java 中 opens 或命令行 --add-opens。

實戰場景速查

  • 動態排序/篩選:前端傳字段名,後端反射取值比較。
  • 通用導出/導入:根據註解/配置動態讀寫對象屬性。
  • 插件化/腳本引擎:動態加載類並調用約定方法。
  • DTO 轉 VO/Map:通過屬性名列表批量提取值。

總結

Java 反射賦予了「運行時感知與操作類結構」的能力。掌握 Method.invoke、Introspector、MethodHandle 三層進階用法,並建立**緩存機制、異常轉換、權限控制**三大護欄,即可在靈活性與性能、安全之間取得平衡。記住:反射是框架的利器,業務代碼的最後手段。

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