Web 應用常見主從表場景(訂單/明細、帳戶/持股)。Struts 1.x 需正確映射表單多行數據到 ActionForm 的 List 屬性,避免手工拼裝。
Struts 使用 Commons BeanUtils 根據表單域 name 屬性自動填充:
account.name → form.getAccount().setName()stocks[0].code → form.getStocks().get(0).setCode()// 帳戶信息
public class Account {
private String name;
private String number;
// getter/setter 省略
}
// 持股明細
public class Stock {
private String code;
private String name;
private BigDecimal price;
private Integer quantity;
// getter/setter 省略
}
import java.util.ArrayList;
public class AutoArrayList extends ArrayList {
private final Class<?> elementType;
public AutoArrayList(Class<?> elementType) {
this.elementType = elementType;
}
@Override
public Object get(int index) {
// 自動擴容並實例化元素
while (index >= size()) {
try {
add(elementType.getDeclaredConstructor().newInstance());
} catch (Exception e) {
throw new RuntimeException("無法實例化 " + elementType, e);
}
}
return super.get(index);
}
}
import org.apache.struts.action.ActionForm;
import java.util.List;
public class AccountStockForm extends ActionForm {
private Account account = new Account(); // 必須初始化
private List<Stock> stocks = new AutoArrayList(Stock.class); // 關鍵:使用自動擴容列表
public Account getAccount() { return account; }
public void setAccount(Account account) { this.account = account; }
public List<Stock> getStocks() { return stocks; }
// 關鍵:setter 保持運行時類型為 AutoArrayList
public void setStocks(List<Stock> stocks) {
this.stocks.clear();
this.stocks.addAll(stocks);
}
}
<html:form action="/showStock">
<fieldset>
<legend>基本信息</legend>
帳戶名: <html:text property="account.name" />
資金帳號: <html:text property="account.number" />
</fieldset>
<fieldset>
<legend>持有股票</legend>
<table id="stockTable">
<tr>
<th>選擇</th><th>股票代碼</th><th>股票名稱</th><th>成本價</th><th>數量</th>
</tr>
<tr>
<td><input type="checkbox" name="check" /></td>
<td><input name="stocks[0].code" size="15" /></td>
<td><input name="stocks[0].name" size="15" /></td>
<td><input name="stocks[0].price" size="15" /></td>
<td><input name="stocks[0].quantity" size="15" /></td>
</tr>
<!-- JS 動態添加行時,name 必須為 stocks[1].code, stocks[2].code ... -->
</table>
</fieldset>
<input type="hidden" name="fileCount" id="fileCount" value="1" />
<input type="button" value="新增行" onclick="addRow()" />
<html:submit value="保存數據" />
</html:form>
function addRow() {
var count = parseInt($('#fileCount').val());
var newRow = '<tr>' +
'<td><input type="checkbox" name="check" /></td>' +
'<td><input name="stocks[' + count + '].code" size="15" /></td>' +
'<td><input name="stocks[' + count + '].name" size="15" /></td>' +
'<td><input name="stocks[' + count + '].price" size="15" /></td>' +
'<td><input name="stocks[' + count + '].quantity" size="15" /></td>' +
'</tr>';
$('#stockTable').append(newRow);
$('#fileCount').val(count + 1);
}
// 刪除行後必須重排所有索引!
function reindexRows() {
$('#stockTable tr:gt(0)').each(function(i) {
$(this).find('input[name^="stocks["]').each(function() {
this.name = this.name.replace(/stocks\[\d+\]/, 'stocks[' + i + ']');
});
});
$('#fileCount').val($('#stockTable tr:gt(0)').length);
}
public class AccountStockAction extends Action {
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {
AccountStockForm asForm = (AccountStockForm) form;
// 直接使用自動填充的對象圖
Account account = asForm.getAccount();
System.out.println("帳戶: " + account.getName() + ", " + account.getNumber());
List<Stock> stocks = asForm.getStocks();
for (int i = 0; i < stocks.size(); i++) {
Stock s = stocks.get(i);
// 過濾空行 (用戶可能留空)
if (s.getCode() != null && !s.getCode().trim().isEmpty()) {
System.out.printf("股票[%d]: %s %s %.2f %d%n",
i, s.getCode(), s.getName(), s.getPrice(), s.getQuantity());
}
}
return mapping.findForward("show");
}
}
<html:form action="/showStock">
<nested:nest property="account">
帳戶名: <nested:text property="name" readonly="true" />
資金帳號: <nested:text property="number" readonly="true" />
</nested:nest>
<table>
<nested:iterate property="stocks" id="stock">
<tr>
<td><nested:text property="code" size="15" readonly="true" /></td>
<td><nested:text property="name" size="15" readonly="true" /></td>
<td><nested:text property="price" size="15" readonly="true" /></td>
<td><nested:text property="quantity" size="15" readonly="true" /></td>
</tr>
</nested:iterate>
</table>
</html:form>
nested:iterate 會自動生成 stocks[0].code 等索引名,與輸入頁面完美對應。
<form-bean name="accountStockForm" type="com.unmi.form.AccountStockForm" />
<action path="/showStock"
name="accountStockForm"
type="com.unmi.action.AccountStockAction"
scope="request">
<forward name="show" path="/show.jsp" />
</action>
account 和 stocks 必須在聲明時初始化,否則 NPEget(index) 實現自動擴容與實例化,解決索引越界setStocks() 使用 clear()+addAll() 保持運行時為 AutoArrayListnested:iterate 自動生成正確索引名| 現象 | 原因 | 解決 |
|---|---|---|
| List 為 null | 未初始化 | 聲明時 new AutoArrayList() |
| IndexOutOfBoundsException | 普通 ArrayList | 使用 AutoArrayList |
| 提交數據亂序/丟失 | 索引不連續 | JS reindexRows() 重排 |
| Setter 後類型變為 ArrayList | 直接賦值 | clear()+addAll() 保持類型 |
| 空行被提交 | 用戶未填寫 | Action 中過濾 code 為空的元素 |