Struts2框架学习之七:避免表单重复提交

  1. 云栖社区>
  2. 博客>
  3. 正文

Struts2框架学习之七:避免表单重复提交

rhwayfun 2015-12-23 22:35:00 浏览871
展开阅读全文

前言

防止表单重复提交在web开发中是一个经常遇到的问题,一般来避免重复提交有两种方式:客户端JavaScript代码实现和服务端代码实现。这里主要介绍服务端的实现方式。在服务端实现表单重复提交的基本原理是:通过创建一个Session对象,并产生一个令牌值,将这个令牌值作为隐藏域随表单一起发送给客户端,同时在Session中保存令牌值。在用户提交表单的时候判断提交参数的令牌值与Session中的是否相等,如果相等则清除,不再使用这个令牌值,,然后执行后续的处理;如果两者不相等,表示已经提交过表单,服务端产生一个新的令牌值并保存到Session中。当用户下次访问的的时候,将新产生的领牌值发送到客户端。

Struts2的实现方式

在Struts2中通过使用拦截器来实现的,机制与前言中采用令牌的方式是一样的。可以通过两种方式实现避免重复表单(实际上就是两个不同的拦截器):token拦截器和tokenSession拦截器。由于在struts-default.xml的默认拦截器栈中并没有将这两个拦截器作为默认实现,所以需要在action中手动添加这两个拦截器。这两种方式的区别在于:使用token拦截器重复提交表单的时候,浏览器会跳转到一个错误页面,而使用更tokenSession拦截器重复提交表单的话是不会跳转的,仍然在成功之后页面。需要注意的是,使用者两个拦截器重复提交表单的时候,都只会向服务器提交一次请求,所以这种方式可以有效降低服务器的负担。

具体的例子

在使用以上拦截器进行测试的时候,需要如下步骤:
步骤一:编写login.jsp、success.jsp和error.jsp三个页面
login.jsp

...
<s:form action="tokenWait" namespace="/" method="post">
        <s:textfield label="用户名" name="user.username"></s:textfield>
        <s:password label="密码" name="user.password"></s:password>
        <!-- 这个标签不能少 -->
        <%-- <s:token></s:token> --%>
        <s:submit value="登录"></s:submit>
    </s:form>
 ...

success.jsp

...
<s:property value="user.username"/>,<%=new Date() %>
...

error.jsp

<html>
<body>
    <!-- 登录失败,请重新登录
    <a href="login.jsp">返回</a> -->
    您已经提交过表单了!
</body>
</html>

步骤二:编写action

package action;

import java.util.ArrayList;
import java.util.List;

import bean.User;

import com.opensymphony.xwork2.ActionSupport;

public class TokenAction extends ActionSupport {

    private static final long serialVersionUID = 1L;
    private User user;

    @Override
    public String execute() throws Exception {
        Thread.sleep(3000);
        List<User> users = new ArrayList<User>();
        users.add(user);
        for (User user : users) {
            System.out.println(user);
        }
        return SUCCESS;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

}

步骤三:配置struts.xml

<!-- 避免表单重复提交 -->
        <action name="token" class="action.TokenAction">
            <!-- 配置Token拦截器 -->
            <interceptor-ref name="defaultStack"></interceptor-ref>
            <interceptor-ref name="token"></interceptor-ref>
            <!-- 如果重复提交,则跳转到error.jsp -->
            <result name="invalid.token">/error.jsp</result>
            <result>/success.jsp</result>
        </action>

        <action name="tokenSession" class="action.TokenAction">
            <!-- 配置TokenSession拦截器 -->
            <interceptor-ref name="defaultStack"></interceptor-ref>
            <interceptor-ref name="tokenSession"></interceptor-ref>
            <!-- 如果重复提交,则跳转到error.jsp -->
            <result name="invalid.token">/error.jsp</result>
            <result>/success.jsp</result>
        </action>

这里需要提出的是,action中name属性为invalid.token的result是不可少的。

步骤四:发布测试
经过测试,发现使用token拦截器在重复提交表单的时候会转到error.jsp,而使用tokenSession拦截器在重复提交表单的时候不会转到error.jsp。

显示等待页面

有时候在action需要处理较长时间的时候,一般是5到10分钟,在这种情况下向用户显示一个等待页面可能会比较友好一些。在Struts2中通过使用execAndWait拦截器就可以非常轻松实现这点。

execAndWait的工作机制:

execAndWait拦截器能够让一个A执行时间超过5分钟的Action在后台运行,并向用户显示一个等待页面。之所以是5分钟是因为这样防止HTTP请求超时。当一个请求到来的时候,execAndWait拦截器会创建一个线程来执行Session,然后返回一个等待页面,这样用户就知道请求在处理中。等待页面包含了自动刷新功能,在超时之前,浏览器会向初始请求的action再次发起请求,以便知道后台action是否已经执行完毕。如果action仍然没有执行完毕,则继续显示等待页面,如果action已经执行完毕,则等待页面将发生跳转,向用户处理结束之后的页面。

execAndWait拦截器有以下几个参数:

  • threadPriority:执行线程的优先级
  • delay:指定在显示等待页面前初始的延迟加载时间,单位是毫秒
  • delaySleepInternal:指定检查后台线程是否执行完毕的时间间隔,必须和delay参数一起使用,单位是毫秒,默认是100毫秒。表示每100毫秒进行一次检查

使用execAndWait拦截器显示等待页面,首先需要编写一个等待页面:

...
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>等待页面</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="refresh" content="3;url=tokenWait.action">
</head>

<body>
    您的请求正在处理,请稍等。
    <span id="time" style="font-size:30px;color:red;font-face:隶书"></span>秒后页面将自动跳转
    <script type="text/javascript">
       var start = 3;
       var step = -1;
       function timer () {
         document.getElementById("time").innerHTML = start;
         if(start > 0){
            start = start + step;
         }
         setTimeout("timer()",1000);
       }
       window.onload = timer;
    </script>
</body>
</html>

在head 标签中需要添加自动刷新meta标签,不然是不会出发自动检查的。在这个等待页面中,表示3秒后就会跳转到成功页面。

之后是添加execAndWait拦截器的配置:

<!-- 显示自动等待页面 -->
        <action name="tokenWait" class="action.TokenAction">
            <result name="wait">/wait.jsp</result>
            <result>/success.jsp</result>
            <interceptor-ref name="defaultStack">
                <!-- 把default方法排序在外,表示不拦截!default.action -->
                <param name="excludeMethods">default</param>
            </interceptor-ref>
            <interceptor-ref name="execAndWait">
                <!-- 把default方法排序在外,表示不拦截!default.action -->
                <param name="excludeMethods">default</param>
                <!-- 等待延迟时间 -->
                <param name="delay">1000</param>
            </interceptor-ref>
        </action>

注意到TokenAction类中,使用Tread.sleep(3000),表示通过让线程休眠的方式延长action的处理时间,还有一点要注意的是struts.xml中execAndWait拦截器的delay参数的值需要小于Thread.sleep(time)的时间。这样就能保证在action处理结束之前完成显示等待页面,不然很可能会直接success.jsp页面了。

网友评论

登录后评论
0/500
评论
rhwayfun
+ 关注