在 Struts 開發中,ActionForm 與領域模型 (Entity/DTO) 之間頻繁需要屬性拷貝。手動編寫大量 setXxx(getXxx()) 既繁瑣又易出錯。Apache Commons BeanUtils 提供 BeanUtils.copyProperties(),但理解其底層反射原理並能手寫簡化版,有助於排查類型轉換、只讀屬性等疑難問題。本文演示基於反射的屬性拷貝核心邏輯並給出完整可運行示例。
import java.lang.reflect.Method;
public class BeanUtil2 {
/**
* 將 obj1 的所有可讀屬性 (getter) 拷貝至 obj2 的同名可寫屬性 (setter)
* @param src 源對象
* @param dest 目標對象 (將被修改並返回)
* @return 修改後的 dest
* @throws Exception 反射調用異常
*/
public static Object copyBeanToBean(Object src, Object dest) throws Exception {
Method[] srcMethods = src.getClass().getMethods();
Method[] destMethods = dest.getClass().getMethods();
for (Method srcM : srcMethods) {
String srcName = srcM.getName();
if (!srcName.startsWith("get")) continue; // 僅處理 getter
String propSuffix = srcName.substring(3); // 去掉 "get"
for (Method destM : destMethods) {
String destName = destM.getName();
if (!destName.startsWith("set")) continue; // 僅處理 setter
if (!destName.substring(3).equals(propSuffix)) continue; // 屬性名匹配
// 調用 src getter
Object value = srcM.invoke(src, (Object[]) null);
// 調用 dest setter
destM.invoke(dest, value);
break; // 找到匹配 setter 即跳出內層循環
}
}
return dest;
}
}
public class User {
private String name;
private String id;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public static void main(String[] args) throws Exception {
User u1 = new User();
u1.setName("zxb");
u1.setId("3286");
User u2 = new User();
u2 = (User) BeanUtil2.copyBeanToBean(u1, u2);
System.out.println(u2.getName()); // 輸出: zxb
System.out.println(u2.getId()); // 輸出: 3286
}
}
public getter/setter;私有屬性需用 getDeclaredMethods() + setAccessible(true)。Integer → String) 會拋 IllegalArgumentException。生產級工具需引入 類型轉換器 (Converter)。Method 對象或使用字節碼生成庫 (如 ASM、ByteBuddy) 生成拷貝類。org.apache.commons:commons-beanutils、org.springframework:spring-beans (BeanWrapper)、com.github.dozermapper:dozer (深拷貝/映射)、org.modelmapper:modelmapper。掌握反射拷貝原理,既能在無第三方依賴環境快速實現需求,又能在引入成熟庫時理解其行為邊界,做到知其然更知其所以然。