一.为什么会出现表单重复提交
1.当用户在表单中填写完信息,点击提交按钮后,肯能会因为没有看到成功信息而在此点击提交按钮,从而导致在服务器端收到两条同样的信息,如果这个信息要保存到数据库中的,那么就会出现两条重复的数据,或者可能出现数据库操作异常。
2.当用户填写表单信息完成后,点击表单提交,即使响应及时,也有可能会出现表单重复提交的情况。一般服务器端的程序在处理完用户提交的信息后,调用的是Request.Dispatcher.forward()方法将用户的请求导向成功页面,用户看到成功信息后,点击刷新,此时浏览器将再次提交用户先前输入的数据,刚才说了这是因为调用Request.Dispatcher.forward()方法返回成功页面的,浏览器锁保留的url是先前表单提交的url,如果采用的是HttpServletResponse.sendRedirect()方法将客户端重定向到成功页面,就不会出现重复提交的问题了。
二.避免表单重复提交的原理
避免表单重复提交有两种方案,一种是在JavaScript脚本上,第二种是在服务器端来阻止表单重复提交,我们主要那么这里我们主要针对的是服务器端来处理表单的重复提交.
1.用访问包含表单的页面,服务器端在这次会话中,创建一个session对象,并产生一个令牌值,将这个令牌值作为隐藏输入域的值,随表单一起发送到客户端,同事将令牌值保存到session中。
2.用户提交页面,服务器端首先判断请求参数中的令牌值和Session中保存到额令牌值是否相等,如果相等,则清楚Session中的令牌值,然后执行数据吃了操作,如果不相等,则提示用户已经提交过表单了,同时产生一个新的令牌值,保存到Session中,当用户重新访问提交数据页面时,将新产生的令牌值作为隐藏输入域的值。
三.使用Struts2来实现防止表单重复提交
Struts2使拦截器来检查表单是否重复提交,他也是采用同步令牌的方式来实现对表单重复提交的判断。
第一步:
首先要在表中使用<s:token>标签,并指定一二令牌的名字,<s:token>标签用来创建一个新的令牌值,并用你所指定的令牌名把令牌值保存到Session中,这个令牌值是随机产生的经过加密的字符序列。
从上图中可以看出,<s:token>标签会在表单中生成两个隐藏字段。第一个隐藏字段的名字是固定的,struts.token.name.第二个隐藏字段的名字是<s:token>标签name属性的值,他的值就是随机产生的经过加密的令牌值。服务器端的拦截器首先根据struts.token.name请求参数找到保存了令牌值的请求参数名user.token,然后在获取user.token请求参数,得到令牌的值。
第二步:
需要在Action配置引用TokenInterceptor或者TokenSessionStoreInterceptor拦截器,这两个拦截器已经在struts-defatul.xml中定义,但没有包含在defaultStatck拦截器中。
我们这里先使用TokenInterceptor拦截器,当表单提交时,TokenInterceptor拦截器获取请求,获取标准的struts.token.name请求参数,得到保存了令牌值的请求参数名,然后再根据这个请求的参数获取令牌值,接着TokenInterceptor根据刚才得到的令牌名,从session中取出<s:token>标签先前保存到session中的令牌值,对两个令牌值进行比较。如果session中的令牌值等于请求参数中的令牌值,那么删除session中的令牌值,并调用handleValidToken()方法,该方法直接调用,他首先调用Action的方法对请求进行处理;如果两个值不相等,那么handelInvalidToken()方法被调用,它首先添加一个Action级别的错误消息到这个Action(Action需要实现ValidationAware,ActionSupport基类实现了该接口),然后直接返回INVALID_TOKEN_CODE结果码,从而跳过Action的执行。INVALID_TOKEN_CODE是一个在TokenInterceptor中定义的静态常量,值为invalid.token
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <!-- 配置文件中只要添加以下配置,那么以后修改配置文件不用重启 tomcat --> <constant name="struts.devMode" value="true" /><!--name:常量的名称 value:常量的值 --> <!--处理中文乱码问题的解决 --> <!--国际化信息内码 --> <constant name="struts.i18n.encodeing" value="UTF-8" /> <package name="default" namespace="/" extends="struts-default"> <action name="token" class="cn.action.RegistAction" > <!-- 配置Token拦截器 --> <interceptor-ref name="defaultStack"></interceptor-ref> <interceptor-ref name="token"></interceptor-ref> <!-- 如果重复提交,则跳转到error.jsp --> <result name="invalid.token">/error.jsp</result> <result name="success">/success.jsp</result> </action> </package> </struts>
action:
package cn.action; import com.opensymphony.xwork2.ActionSupport; public class RegistAction extends ActionSupport{ private String name; private String pwd; @Override public String execute() throws Exception { for (int i = 0; i < 5000000; i++) { System.out.println("===="); } return SUCCESS; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } }
注册页面:
<body>
<s:actionerror/> <s:form action="token"> <s:token name="user.token"></s:token> <s:textfield label="用户名" name="name"></s:textfield> <s:password label="密码" name="pwd"></s:password> <s:submit value="提交"></s:submit> </s:form> </body>
如果用户连续提交两次,便会走造成表单重复提交,会跳到该页面显示错误信息。也可以通过配置来该错误提示信息。