在 Struts 2 中向後台傳遞物件陣列(或 List)時,常遇到 Action 屬性為 null 的困擾。根因在於:單個物件有構造器可自動實例化,而陣列/集合必須顯式初始化,否則 OGNL 無法在空引用上設值。
public class UserAction extends ActionSupport {
// 必須初始化!否則前端傳值永遠為 null
private List<User> users = new ArrayList<>();
// Getter/Setter 必備(private 推薦)
public List<User> getUsers() { return users; }
public void setUsers(List<User> users) { this.users = users; }
public String execute() {
// users 已自動填充
return SUCCESS;
}
}
關鍵點:
public 或 private(需 Getter/Setter),推薦 private 封裝。new 出實例,這是最常被忽略的步驟。User 需為標準 JavaBean(有無參構造器、屬性 Getter/Setter)。JSP/HTML 表單使用 屬性名[索引].屬性 格式:
<!-- 第 0 個用戶 -->
<input name="users[0].id" value="1">
<input name="users[0].name" value="張三">
<!-- 第 1 個用戶 -->
<input name="users[1].id" value="2">
<input name="users[1].name" value="李四">
索引 必須連續從 0 開始,否則中間空缺會導致綁定失敗或產生 null 元素。
若前端允許動態增減行,需同步維護索引連續性:
function addRow() {
const index = document.querySelectorAll('[name^="users["]').length / 2; // 簡易計算
// 插入新行,name="users[" + index + "].id" ...
}
function removeRow(btn) {
// 移除行後,重新排序所有 users[index].* 的 name 屬性
document.querySelectorAll('[name^="users["]').forEach((el, i) => {
el.name = el.name.replace(/users\[\d+\]/, 'users[' + Math.floor(i/2) + ']');
});
}
若業務鍵值非連續整數,改用 Map<String, User>:
// Action
private Map<String, User> userMap = new LinkedHashMap<>();
// 前端
<input name="userMap['keyA'].id" value="1">
<input name="userMap['keyA'].name" value="張三">
OGNL 會自動實例化 User 並 put 入 Map,無需預先 put 空值。
execute() 打斷點,檢查集合大小與元素屬性。struts.devMode=true),控制台會輸出 OGNL 綁定詳情。null,檢查:name 屬性拼寫、索引連續性、元素類別是否有無參構造器、集合是否初始化。