WEB 应用中一般都会处理主从表的信息, 或者称之为头层与行层的一对多的关系数据,如订单头/订单明细. 对于这种关系数据提交到后台的 Struts 的 ActionForm 的话, 这个 ActionForm 就要好好的设计一下, 不然会给自已带来许多额外的代码. 比如有的人的处理方法就是把页面提交到后台的毫无关系的散装数据非常吃力的拼凑一对多的关系对象出来.
下面举一个如今非常现实的关于股票的例子, 简单的应用场景是: 记录某个帐户所持有的股票信息,提交到后台,然后显示出来. 输入页面如下图
帐户信息包括帐户名和资金帐号;持有股票的每一行信息包括股票代码, 股票名称, 成本价, 股票数量. 股票行可以动态增删.
输入页面 input.jsp
后台处理类图
为了简化不必要的代码, 我们要实现的终及目标是: 在输入页面上点击 "保存数据" 按钮, 由 Struts 的 RequestProcessor.processPopulate() 方法把页面提交的基本信息组装到 AccountStockForm 的 account 的对应属性中,股票行信息对应生成一个 Stock 实例加到 AccountStockForm的 List 属性 stocks 中, 后续在 AccountStockAction 中直接处理account和stocks属性就非常简单了. AccountStockForm在这里只作为一个壳.
一: struts-config.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
"http://struts.apache.org/dtds/struts-config_1_3.dtd">
<struts-config>
<form-beans>
<form-bean name="accountStockForm"
type="com.unmi.form.AccountStockForm"/>
</form-beans>
<action-mappings>
<action path="/showStock" name="accountStockForm"
type="com.unmi.action.AccountStockAction" scope="request">
<forward name="show" path="/show.jsp"/>
</action>
</action-mappings>
</struts-config>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
"http://struts.apache.org/dtds/struts-config_1_3.dtd">
<struts-config>
<form-beans>
<form-bean name="accountStockForm"
type="com.unmi.form.AccountStockForm"/>
</form-beans>
<action-mappings>
<action path="/showStock" name="accountStockForm"
type="com.unmi.action.AccountStockAction" scope="request">
<forward name="show" path="/show.jsp"/>
</action>
</action-mappings>
</struts-config>
二: 输入页面 input.jsp, 注意表单域命名
<html:form action="/showStock">
<h3>记录持有的股票<br></h3>
<fieldset>s<legend>基本信息</legend>
<table width="100%" border=0><tr>
<td>帐户名:<html:text property="account.name"/></td>
<td>资金帐号:<html:text property="account.number"/></td>
</tr></table>
</fieldset>
<br>
<fieldset><legend>持有股票</legend>
<table width=100% border=0 id="stockTable">
<tr>
<td><input type="checkbox" onclick="checkAll(this)"></td>
<td>股票代码</td>
<td>股票名称</td>
<td>成本价</td>
<td>股票数量</td>
</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>
</table>
</html:form>
<html:form action="/showStock">
<h3>记录持有的股票<br></h3>
<fieldset>s<legend>基本信息</legend>
<table width="100%" border=0><tr>
<td>帐户名:<html:text property="account.name"/></td>
<td>资金帐号:<html:text property="account.number"/></td>
</tr></table>
</fieldset>
<br>
<fieldset><legend>持有股票</legend>
<table width=100% border=0 id="stockTable">
<tr>
<td><input type="checkbox" onclick="checkAll(this)"></td>
<td>股票代码</td>
<td>股票名称</td>
<td>成本价</td>
<td>股票数量</td>
</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>
</table>
</html:form>
例如输入框名 account.name 提交后能设置到 accountStockForm 的account的name属性
输入框名为 stocks[0].code 提交后会设置到 accountStockForm 的 List stocks的第一个元素的code属性.以此类推
在提交表单前要重排行层的索引,从 0 起, 否则到后右的 Form 会一些空数据.
三: AccountStockForm 的关键代码
private Account account = new Account();
private List stocks = new AutoArrayList(Stock.class);
public void setStocks(List stocks)
{
this.stocks.clear();
this.stocks.addAll(stocks);
}
private Account account = new Account();
private List stocks = new AutoArrayList(Stock.class);
public void setStocks(List stocks)
{
this.stocks.clear();
this.stocks.addAll(stocks);
}
定义了两个属性,分别是一个bean(Account,接受基本信息)和一个List(stocks,接受股票行信息),注意这两个属性必须初始化,不然在表单提交后会出现空指针错误. setStocks方法是让stocks属性永远保有持是一个 AutoArrayList 实例. 这样在表单提交后设置值是总能调用 AutoArrayList 的 get(int index) 方法.
四: 自定义的 AutoArrayList
public class AutoArrayList extends ArrayList {
理解为什么要继承一个ArrayList, 覆写get(int index)方法要简单了解 Struts 处理提交数据的工作原理: 大致如下: 页面提交后, 由 ActionServlet交给RequestProcessor的processPopulate()方法,由processPopulate()方法收集请求数据,放在map中,key为表单域的name属性,如 name, account.name, stocks[0].code. 然后借助于 Common-beanutils 工具包设置到 ActionForm 的相应属性中
private Class itemClass;
public AutoArrayList(Class itemClass) {
this.itemClass = itemClass;
}
public Object get(int index) {
try {
while (index >= size()) {
add(itemClass.newInstance());
}
} catch (Exception e) {
e.printStackTrace();
}
return super.get(index);
}
}
public class AutoArrayList extends ArrayList {
private Class itemClass;
public AutoArrayList(Class itemClass) {
this.itemClass = itemClass;
}
public Object get(int index) {
try {
while (index >= size()) {
add(itemClass.newInstance());
}
} catch (Exception e) {
e.printStackTrace();
}
return super.get(index);
}
}
如果key是简单的'name',直接form.setName(map.get('name'));
如果key是'account.name', 执行的操作是 form.getAccount().setName(map.get('account.name');
如果key是'stocks[0].code', 它可以对应到数据或集合中,如对于数组 form.stocks[0].code=map.get('stocks[0].code'); 对于集合(List) form.get(0).setCode(map.get('stocks[0].code'))
从上也能理解为什么 form 中的那两个属性必须实始化,不然会出现空指针错. 而且为什么 stocks 要用 AutoArrayList 实例化, 避免出现索引越界的错误.
五: 在 AccountStockAction 中可以打印出提交的数据
AccountStockForm asForm = (AccountStockForm)form;
Account account = asForm.getAccount();
System.out.println("Account Name:"+account.getName()+
" Number:"+account.getNumber());
List stocks = asForm.getStocks();
for (int i=0; i<stocks.size() ;i++)
{
Stock stock = (Stock)stocks.get(i);
System.out.println("Stock["+i+"]Code:"+stock.getCode()+
" Name:"+stock.getName()+
" Price:"+stock.getPrice()+
" Quantity:"+stock.getQuantity());
}
return mapping.findForward("show");
AccountStockForm asForm = (AccountStockForm)form;
Account account = asForm.getAccount();
System.out.println("Account Name:"+account.getName()+
" Number:"+account.getNumber());
List stocks = asForm.getStocks();
for (int i=0; i<stocks.size() ;i++)
{
Stock stock = (Stock)stocks.get(i);
System.out.println("Stock["+i+"]Code:"+stock.getCode()+
" Name:"+stock.getName()+
" Price:"+stock.getPrice()+
" Quantity:"+stock.getQuantity());
}
return mapping.findForward("show");
在Action中就能直接取用提交来的数据了,不需要 getParameterValues()了.
六: 最后一步, 对于这样的 ActionForm 我们应该如何显示出来呢,我们用了 nested 标签 (show.jsp)