前言
防止表单重复提交,这是个很重要的知识点,而且经常会用到。当用户提交了一个表单,此时,地址栏显示的是处理这个表单的Action的地址,若此时刷新,则会重新发送一次表单数据,即又进行了一次提交,若这个Action是用来处理用户注册的,那么重复提交会再一次向数据库中插入之前已经插入的数据,这显然不是我们想要的。有两种方法,可以防止表单重复提交,一种是用Action的重定向,一种是用Session Token(Session令牌)。
第一种方法,Action处理完用户提交的数据后,重定向到另一个Action或是一个页面,地址栏会发生变化,使用户提交后,所停留的位置,不是当前处理数据的Action,这样用户再刷新时,就不会再次执行这个Action了,就会避免表单重复提交的问题了。
第二种方法,是一种很经典的处理这个问题的机制。这种方法是在用户要提交的表单中,加入一个<s:token>
标签,这样,当浏览器第一次访问这个带有<s:token>
标签的页面时,在服务器中,解析<s:token>
标签的类,会生成一个随机的字符串(这个字符串,查看网页的源代码可以看到),并且发送给客户端的浏览器,同时,在服务器中,会把这个随机字符串保存到用户的session对象中。当第一次提交表单时,在服务器中,会比较客户端和服务器中分别保存的这个随机字符串,因为是第一次提交,所以这两个字符串相等,然后进行正常的业务处理。第一次提交后,在服务器中的session中保存的这个随机字符串,会改变为其他的随机值,注意,这是很重要的一步!此时,地址栏停留在处理用户提交数据的Action中,客户端中保存的随机字符串没有改变,若是刷新页面,即重复提交,服务器再进行两个字符串的比较,会不相等,就会跳转到name为invalid.token的结果页面中,这样就会防止表单重复提交了。
我们根据具体的案例来实现上面两个不同防止表单提交的方法。
案例
本案例主要的设计如下图所示:
需求描述:三个功能:register.jsp -注册页面,用户注册保存到数据库中。保存成功跳转到用户信息列表list.jsp.然后在list.jsp页面可以进行修改操作。其中会在register.jsp表单中添加token标签,让其生成随机值,浏览器跟服务器各一份。
数据库表结构
CREATE TABLE employee (
id INT PRIMARY KEY AUTO_INCREMENT,
empName VARCHAR(20),
workDate DATE -- 入职时间
)
代码如下
EmployeeAction.java
public class EmployeeAction extends ActionSupport implements ModelDriven<Employee> {
private Employee employee = new Employee();
private EmployeeService service = new EmployeeService();
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
@Override
public Employee getModel() {
return employee;
}
//注册
public String register(){
try {
service.save(employee);
return list();
} catch (Exception e) {
e.printStackTrace();
return INPUT ;
}
}
/**
* 列表显示
*/
public String list(){
try {
List<Employee> employeeList =service.getAll();
for (Employee employee : employeeList) {
System.out.println(employee.getWorkDate());
}
//保存到域对象
ActionContext.getContext().getContextMap().put("employeeList", employeeList);
return "list";
} catch (Exception e) {
e.printStackTrace();
}
return ERROR;
}
/**
* 跳转到修改视图
*/
public String viewUpdate(){
try {
Employee emp = service.findById(employee.getId());
//保存到值栈中
ValueStack stack = ActionContext.getContext().getValueStack();
Employee pop = (Employee)stack.pop();
stack.push(emp);
return "ViewUpdate";
} catch (Exception e) {
e.printStackTrace();
}
return ERROR;
}
/**
* 修改
*/
public String update(){
try {
service.update(employee);
return list();
} catch (Exception e) {
e.printStackTrace();
}
return ERROR;
}
}
Register.jsp
<form action="${pageContext.request.contextPath }/employee_register.action" method="post">
<s:token></s:token>
员工名:<input type="text" name="empName">
<s:fielderror fieldName="employee.empName"></s:fielderror><br/>
入职时间:<input type="text" name="workDate">
<s:fielderror fieldName="employee.workDate"></s:fielderror><br/>
<input type="submit" value="注册">
</form>
List.jsp
<table align="center" border="1px" cellpadding="0px" cellspacing="0px">
<tr>
<th>序号</th><th>编号</th><th>员工名</th><th>入职时间</th><th>操作</th>
</tr>
<s:if test="%{#request.employeeList!=null}">
<s:iterator var="emp" value="%{#request.employeeList}" status="vs">
<tr>
<td><s:property value="#vs.count"/></td>
<td><s:property value="#emp.id"/></td>
<td><s:property value="#emp.empName"/></td>
<td><s:date name="%{#emp.workDate}"/></td>
<td><s:a href="employee_viewUpdate?id=%{#emp.id}">修改</s:a></td>
</tr>
</s:iterator>
</s:if>
<s:else>
<tr>
<td colspan="5">没有员工信息,请先注册!</td>
</tr>
</s:else>
</table>
Update.jsp
<s:form action="/employee_update">
<s:hidden name="id" ></s:hidden>
<s:textfield name="empName"/><br/>
<s:date name="workDate"/>
<s:submit value="修改"></s:submit>
</s:form>
Struts.jsp
<struts>
<!-- 修改主题 (当前项目所有的标签都用此主题)-->
<constant name="struts.ui.theme" value="simple"></constant>
<package name="employee" extends="struts-default">
<global-results>
<result name="error">/error.jsp</result>
</global-results>
<action name="employee_*" class="edu.action.EmployeeAction"
method="{1}">
<!-- 防止表单重复提交,第二步: 配置" 防止表单重复提交拦截器" -->
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="token">
<!-- 指定拦截哪些方法需要防止表单重复提交(register) -->
<param name="includeMethods">register</param>
</interceptor-ref>
<!-- 防止表单重复提交,第三步: 如果用户重复提交了跳转到指定的错误页面 -->
<result name="invalid.token" type="redirectAction">employee_list</result>
<!-- 如果不用重定向默认是转发 地址不会变type="redirectAction" -->
<result name="input">/register.jsp</result>
<result name="ViewUpdate">/WEB-INF/ViewUpdate.jsp</result>
<result name="list">/WEB-INF/list.jsp</result>
</action>
</package>
</struts>
可以看到在访问register页面时的源代码如下:
<form action="/d909_Demo/employee_register.action" method="post">
<input type="hidden" name="struts.token.name" value="token" />
<input type="hidden" name="token" value="6FY7JZ0ME5YTZ809L2LR7EKWPHPXM77G" />
员工名:<input type="text" name="empName">
<br/>
入职时间:<input type="text" name="workDate">
<br/>
<input type="submit" value="注册">
</form>
服务器自动生成随机值,用于第二次访问时的比较。这样就可以防止表单重复提交了。
其他像service层、dao层代码这里就不再赘述了。
第二种防止表单重复提交的方式:
只需要把Action中register页面返回值在result中配置成重定向即可。
修改代码如下:
Action中的register方法
public String register(){
try {
service.save(employee);
return "addSuccess";
} catch (Exception e) {
e.printStackTrace();
return INPUT ;
}
}
struts.xml增加结果视图如下:
<result name="addSuccess" type="redirectAction">employee_list</result>
这样每次注册完之后,地址栏发生变化,避免了表单重复提交。
地址栏显示:http://localhost:8080/d909_Demo/employee_list.action