论坛首页 Java企业应用论坛

《研磨struts2》第十七章 防止重复提交

浏览 3112 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-06-18   最后修改:2012-06-18


17.1  
什么是重复提交

所谓重复提交,就是用户在页面多次点击提交按钮,或者通过不断刷新页面,把已经提交过的数据多次向后台提交。

重复提交对于查询类的功能,问题还不是太大,但是对于新增或是修改类的功能,就有可能凭空的出现很多同样信息的重复数据,从而导致业务功能出现错误。

用一个实例来让大家体会一下重复提交的问题,来实现一个订单新增的功能,订单号一般都是由后台自动生成,前台页面只要传递订购人、订购商品和订购数量等订单的具体信息过来就可以了。为了示例的简洁,只是从前台传递一个订购的商品和一个订购的数量,一共两个值就可以了,就不去处理什么订单的主记录、子记录等复杂的功能了。

1:实现Action

写一个处理订单的Action,接收前台传递过来的数据,然后输出即可。为了表示会花一定的时间来进行业务处理,在execute方法里面,让线程休息6秒,示例代码如下:

 

java代码:
  1. public class TokenAction extends ActionSupport{  
  2.     private String productId;  
  3.     private int orderNum;  
  4.       
  5.     public String getProductId() {  
  6.         return productId;  
  7.     }  
  8.     public void setProductId(String productId) {  
  9.         this.productId = productId;  
  10.     }  
  11.     public int getOrderNum() {  
  12.         return orderNum;  
  13.     }  
  14.     public void setOrderNum(int orderNum) {  
  15.         this.orderNum = orderNum;  
  16.     }  
  17.     public String execute() throws Exception {  
  18.         System.out.println("预定的产品编号是:"+this.productId+",预定数量为:"+this.orderNum);   
  19.         Thread.sleep(6000L);  
  20.         System.out.println("处理完成!");  
  21.         return SUCCESS;  
  22.     }  
  23. }  

2:配置Action

在struts.xml中配置Action,示例如下:

 

java代码:
  1. <package name="helloworld" extends="struts-default">  
  2.     <action name="tokenAction" class="cn.javass.token.TokenAction">  
  3.         <result>/token/list.jsp</result>  
  4.     </action>  
  5. </package>  

 

java代码:
  1. 3:制作新增页面  
  2. 提供新增页面来让用户输入:预定的产品编号和预定的数量,然后提交到上面的Action,示例代码如下:  

 

java代码:
  1. <%@ page language="java" contentType="text/html; charset=gb2312"  
  2.     pageEncoding="gb2312"%>  
  3. <html>  
  4. <head>  
  5. <meta http-equiv="Content-Type" content="text/html; charset=gb2312">  
  6. <title>Insert title here</title>  
  7. </head>  
  8. <body>  
  9. <%@ taglib prefix="s" uri="/struts-tags"%>  
  10. <s:form action="/tokenAction.action" method="post">  
  11.     <s:textfield name="productId" label="预定的产品编号"/>  
  12.     <s:textfield name="orderNum" label="预定的数量"/>  
  13.     <s:submit value="提交"/>  
  14. </s:form>  
  15. </body>  
  16. </html>  

4:运行测试

运行新增页面,填写数据,然后点击提交。由于Action中,让当前线程sleep了6秒钟,所以必须等6秒之后才能正确的结束调用。

如果这个时候用户乖乖的等足6秒,自然没有什么问题。但是,如果用户在第一次提交还没有响应的时候,又再次点击了提交按钮,则同样的请求再次被提交,这时候,如果不加任何控制的话,同样的订单就将被处理两次了。

可以在控制台中看到输出以下信息:

 

java代码:
  1. 预定的产品编号是:1,预定数量为:2  
  2. 预定的产品编号是:1,预定数量为:2  
  3. 处理完成!  
  4. 处理完成!  

很明显数据重复了,这种重复提交的问题,会给业务处理带来很多的麻烦,应该在开发中杜绝这个问题。

使用Struts2框架来进行开发的话,它已经替我们想好了应对之策,还专门为此提供了<s:token/>标签,接下来就来具体看看它的使用。

 

私塾在线网站原创《研磨struts2》系列

转自请注明出处:【http://sishuok.com/forum/blogPost/list/0/4149.html

欢迎访问http://sishuok.com获取更多内容

   发表时间:2012-06-18  



17.2  
使用<s:token/>标签

17.2.1  使用<s:token/>标签入门

Token也被称做令牌,所以使用<s:token/>标签防止重复提交,也常被称做使用令牌防止重复提交。

1:修改页面

       <s:token/>标签的使用非常简单,只需要在提交页面的<s:form>标签内加上子标签<s:token/>就可以了。

如果把提交页面当做重复提交时返回的结果页面的话,通常还需要在这个页面上引用重复提交时的错误信息。重复提交的错误信息以动作错误信息的方式存在,所以我们只要用<s:actionerror/>标签来引用即可。示例代码如下:

 

java代码:
  1. <%@ page language="java" contentType="text/html; charset=gb2312"  
  2.     pageEncoding="gb2312"%>  
  3. <html>  
  4. <head>  
  5. <meta http-equiv="Content-Type" content="text/html; charset=gb2312">  
  6. <title>Insert title here</title>  
  7. </head>  
  8. <body>  
  9. <%@ taglib prefix="s" uri="/struts-tags"%>  
  10. <s:actionerror/>  
  11. <hr>  
  12. <s:form action="/tokenAction.action" method="post">  
  13.     <s:token/>  
  14.     <s:textfield name="productId" label="预定的产品编号"/>  
  15.     <s:textfield name="orderNum" label="预定的数量"/>  
  16.     <s:submit value="提交"/>  
  17. </s:form>  
  18. </body>  
  19. </html>  

2:修改struts.xml

使用<s:token/>标签,必须为Action引用名为token的预定义拦截器,它并不在defaultStack拦截器栈里,所以需要手动的引用token拦截器。

如果有重复提交的行为,Struts2将跳转到这个Action定义的名为invalid.token的Result,因此,需要修改struts.xml,示例代码如下:

 

java代码:
  1. <package name="helloworld" extends="struts-default">  
  2.     <action name="tokenAction" class="cn.javass.token.TokenAction">  
  3.         <interceptor-ref name="token"/>  
  4.         <interceptor-ref name="defaultStack"/>  
  5.         <result>/token/list.jsp</result>  
  6.         <result name="invalid.token">/token/add.jsp</result>  
  7.     </action>  
  8. </package>  

我们并没有修改TokenAction。那么,现在再次访问订单提交页面,且再次两次提交,可以看到页面跳转到invalid.token指定的Result,其实就是原来的提交页面,并输出重复提交的提示信息,如图所示:

图17.1 页面被重定向到指定的invalid.token结果

再看看控制台的输出,应该只有一次输出了,示例如下:

 

java代码:
  1. 预定的产品编号是:1,预定数量为:2  
  2. 处理完成!  

可以看到,重复的订单数据只被处理了一次,已经正确的处理了重复提交的问题。但是,还有一个小小的遗憾:页面停到了第二次提交被重定向到invalid.token结果上,而不能正确的走到第一次提交的success结果上。

17.2.2 <s:token/>的原理

为什么简简单单的引用<s:token/>标签,就能够实现防止重复提交的功能呢?

       下面来探究一下<s:token/>的运行原理,对于<s:token/>标签和token拦截器的两部分作用要分开来看:

  • <s:token/>标签在页面初始化的时候,向session中写入了一个名为struts.token的属性,其值是一个随机数;然后在表单中生成了两个隐藏域:struts.token.name域用来记录session中写入属性的名字,struts.token域记录了session的struts.token值一模一样的随机数。
  • token拦截器在请求提交的时候,在Action运行之前,比较session的struts.token属性和表单的struts.token隐藏域的值,如果这两个值相等,则移除session的struts.token属性,然后执行execute方法;如果不相等,则重定向到名为invalid.token的结果。

来把上面的两件事情画成流程图,估计比枯燥的文字更容易让人理解。如下图所示:

图16.2 使用令牌防止重复提交流程图

也许,有些朋友会说,既然struts.token隐藏域记录的值和session的struts.token属性记录的值一模一样,那么,在token拦截器运行的时候,比较的结果还可能不相等吗?

当然是可能的,因为token拦截器的作用在重复提交的时候才能显现出来。一起来分析分析,按照运行顺序,先后发生了如下的几件事情:

  • 首先,用户访问页面,这时候,<s:token/>标签要向session写入struts.token属性,假设这个随机数为123456789。那么,<s:token/>标签也同时要向<s:form/>写入名为struts.token的隐藏域,它的值也是123456789。
  • 然后,用户第一次提交这个页面,这时候,session中的struts.token属性和表单中的struts.token隐藏域的值都为123456789,判断为相等,所以token拦截器移除了session中的struts.token这个属性,整个防止重复提交的关键就在这步,然后继续运行execute方法,所以,这次提交是有效的。
  • 在第一次请求还没有运行完,用户再一次提交了这个页面。这时候,session中已经没有了struts.token属性,已经在第一次请求时被token拦截器移除了,但是表单中仍有struts.token隐藏域,所以二者肯定不相等了,那么,这次请求就被认为是重复请求,重定向到invalid.token指定的Result。

       补充说明:如果指定了<s:token/>标签的name属性,那么对session写入的属性名就不再是默认的struts.token,而是这个name值;同理对表单写入的隐藏域名也不再是默认的struts.token,也是这个name值了。在理解<s:token/>标签和token拦截器的作用时,基本不用理会struts.token.name隐藏域的作用。

 

私塾在线网站原创《研磨struts2》系列

转自请注明出处:【http://sishuok.com/forum/blogPost/list/0/4150.html

欢迎访问http://sishuok.com获取更多内容

0 请登录后投票
   发表时间:2012-06-18  


17.3  
更强大的tokenSession拦截器

回顾一下上面<s:token/>标签和token拦截器连用,解决了重复提交问题,用法极其简单:Action引用token拦截器,<s:form/>标签内加一个<s:token/>标签,两句话就足以概括,一分钟就足以做完。

但是结果稍稍有点遗憾:对于一次正常提交和一次重复提交,使用token拦截器会使得浏览器最终重定向到invalid.token指定的Result。那么,有没有更好的办法可以让“重复提交”看起来好像没有发生过,浏览器最终跳转到正常提交指定的Result呢?

       当然有,而且做法同样简单,把token拦截器换为tokenSession拦截器即可。tokenSession拦截器与token拦截器唯一的不同是在判断某个请求为重复请求之后,并不是立即重定向到名为invalid.token的Result,而是先阻塞这个重复请求,直到浏览器响应最初的正常请求,然后就可以跳转到处理正常请求后的Result了。

因此,只需要修改struts.xml即可,示例如下:

 

java代码:
  1. <package name="helloworld" extends="struts-default">  
  2.     <action name="tokenAction" class="cn.javass.token.TokenAction">  
  3.         <interceptor-ref name="tokenSession"/>  
  4.         <interceptor-ref name="defaultStack"/>  
  5.         <result>/token/list.jsp</result>  
  6.     </action>  
  7. </package>  

只做了两点修改:

  • 由引用token拦截器变为引用tokenSession拦截器。
  • 不再需要名为invalid.token的Result。

再次运行看看,虽然重复提交了,但是仍然正常跳转到success指定的页面了,而且后台也只是处理了一次。

 

私塾在线网站原创《研磨struts2》系列

转自请注明出处:【http://sishuok.com/forum/blogPost/list/0/4151.html

欢迎访问http://sishuok.com获取更多内容

0 请登录后投票
   发表时间:2012-06-18  
判断重复提交的地方有加锁吧??
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics