在參數傳遞、表單綁定、緩存序列化等場景中,JavaBean 與 Map 互轉是高頻需求。本文提供基於Java 內省與 Apache Commons BeanUtils 兩套生產級實現,並分析優劣。
import java.beans.*;
import java.lang.reflect.Method;
import java.util.*;
public class BeanMapConverter {
/** JavaBean → Map:過濾 class 屬性,只讀取可讀屬性 */
public static Map<String, Object> beanToMap(Object bean) {
if (bean == null) return Collections.emptyMap();
Map<String, Object> map = new HashMap<>();
try {
BeanInfo info = Introspector.getBeanInfo(bean.getClass());
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
String key = pd.getName();
if (!"class".equals(key)) { // 過濾 Object.class 產生的 class 屬性
Method getter = pd.getReadMethod();
if (getter != null) {
map.put(key, getter.invoke(bean));
}
}
}
} catch (Exception e) {
throw new RuntimeException("Bean轉Map失敗: " + bean.getClass(), e);
}
return map;
}
/** Map → JavaBean:只設置可寫屬性,類型自動轉換 */
public static void mapToBean(Map<String, Object> map, Object bean) {
if (map == null || bean == null) return;
try {
BeanInfo info = Introspector.getBeanInfo(bean.getClass());
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
String key = pd.getName();
if (map.containsKey(key)) {
Method setter = pd.getWriteMethod();
if (setter != null) {
Object value = map.get(key);
// 簡單類型轉換:String → 基本類型/包裝類
setter.invoke(bean, convertType(value, pd.getPropertyType()));
}
}
}
} catch (Exception e) {
throw new RuntimeException("Map轉Bean失敗: " + bean.getClass(), e);
}
}
/** 基礎類型轉換輔助 */
private static Object convertType(Object value, Class<?> targetType) {
if (value == null) return null;
if (targetType.isAssignableFrom(value.getClass())) return value;
String str = value.toString();
if (targetType == int.class || targetType == Integer.class) return Integer.parseInt(str);
if (targetType == long.class || targetType == Long.class) return Long.parseLong(str);
if (targetType == double.class || targetType == Double.class) return Double.parseDouble(str);
if (targetType == boolean.class || targetType == Boolean.class) return Boolean.parseBoolean(str);
// 可擴展:Date、Enum、BigDecimal 等
return value;
}
}
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
// Bean → Map
Map<String, Object> map = BeanUtils.describe(bean); // 含 class 鍵,需自行移除
// Map → Bean
BeanUtils.populate(bean, map); // 自動類型轉換、巢狀屬性、集合填充
| 維度 | 內省實現 | BeanUtils |
|---|---|---|
| 依賴 | 無 | commons-beanutils + commons-logging |
| 性能 | 較快(直接反射調用) | 稍慢(內部緩存 PropertyDescriptor,首次較慢) |
| 類型轉換 | 需手寫 | 內建 ConvertUtils,支持自定義 Converter |
| 巢狀屬性 | 不支持 | 支持 address.city 等 OGNL 風格 |
| 只讀/只寫屬性 | 可精確控制 | 自動跳過無 getter/setter |
| 異常處理 | 統一拋 RuntimeException | 拋 InvocationTargetException/IllegalAccessException |
MapStruct 編譯期生成代碼(零反射)Introspector.getBeanInfo() 會包含 Object 類的 class 屬性,必須過濾ConvertUtils.register() 自定義類型轉換器掌握這兩套方案,可覆蓋 95% 的 Bean-Map 轉換場景。核心原理皆基於 PropertyDescriptor 讀寫方法元數據,理解內省機制有助於排查疑難雜癥。