場景:用戶自定義列表顯示列與順序(字段名存於數據庫),前端傳遞 JSON 數組 ["name", "age", "email"],後端需按此順序從對象提取值並生成 JSON。若用 if-else 或 switch 硬編碼,維護成本極高;反射提供了優雅的動態解決方案。
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 前綴),耦合度較高。
利用 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 通過 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); // 精確類型調用,無裝箱
}
}
| 方式 | 平均耗時 (ns/op) | 相對直接調用 |
|---|---|---|
直接調用 person.getName() | ~1.2 | 1.0x (基準) |
| MethodHandle (緩存) | ~2.5 | ~2.1x |
| Method.invoke (緩存 Method) | ~15 | ~12.5x |
| Method.invoke (每次 getMethod) | ~120 | ~100x |
結論:高頻熱點路徑**必須緩存 Method/MethodHandle**;極致性能場景選 MethodHandle;一般業務 Method.invoke + 緩存足矣。
method.setAccessible(true) 破壞封裝,僅在受信代碼(框架/工具類)中使用,業務代碼慎用。NoSuchMethodException, IllegalAccessException, InvocationTargetException,轉為業務異常拋出。Method、MethodHandle 本身線程安全,緩存用 ConcurrentHashMap 或雙重檢查鎖。module-info.java 中 opens 或命令行 --add-opens。Java 反射賦予了「運行時感知與操作類結構」的能力。掌握 Method.invoke、Introspector、MethodHandle 三層進階用法,並建立**緩存機制、異常轉換、權限控制**三大護欄,即可在靈活性與性能、安全之間取得平衡。記住:反射是框架的利器,業務代碼的最後手段。