[size=medium][b][color=red]表单重复提交现象[/color][/b][/size]
[color=red]导致表单重复提交的现象可以分为一下两种情况:[/color]
[color=red]同步令牌的基本原理[/color]
[color=red]Struts2 的实现方式[/color]
[b][color=red]----使用 token 拦截器---[/color][/b]
struts.xml
login.jsp
index.jsp
[b][color=red]----使用 tokenSession 拦截器---[/color][/b]
---使用 ExecAndWait拦截器
action.java login.jsp index.jsp 和上面的一样
wait.jsp
[color=red]导致表单重复提交的现象可以分为一下两种情况:[/color]
1.多次单击提交按钮
对响应来不及。用户没有看到提交后的结果,导致用户频繁多次的点击提交按钮。
2.执行刷新操作
对响应及时,也有可能出现表单重复提交现象。服务器段的程序在处理哟过户提交哦啊的信息后,调用RewuestDispatcher.forward()方法,将哟过户请求导向成功的页面,用户看到成功信息后,对成功页面执行花心操作,这是浏览器将再次提交用户先前输入的信息。
[color=red]同步令牌的基本原理[/color]
在服务器端避免表单重复提交,通常采用同步令牌的方式来实现。同步令牌的基表原理如下:
1:服务器端在处理客户端请求时,创建一个Session对象和一个令牌(例如 token1)。然后 将token1作为影藏域随处理结果一起发送到客户端,同事将token1保存到session中
2.服务器端在处理到达的请求之前,将请求中的token1与保存在当前用户session中的值进行比较,肩擦这两个值是否匹配。
3.如果相等。表示用户是第一次提交表单,则清楚session中的token1,然后执行数据处理操作,同时产生一个新的令牌值(例如 token2) 然后保存到session中,当用户重新访问提交数据页面时,将新产生的token2做为表单影藏域的值。
4.如果用户重复提交,客户端传递过来的令牌值是token1,而服务器端的令牌为token2,这两个令牌的值不等,于是不在对用户的请求进行提交,从而有效防止表单重复提交发生。
[color=red]Struts2 的实现方式[/color]
Struts2框架提了token标签,使用该标签,需要指定一个令牌的名字,例如:<s:token name="user.token"/> token标签将创建一个新的令牌值,并根据指定的令牌名称将令牌值保存到session中。
token标签最终将会在表单中生成两个影藏字段。请求包含token标签的表单文件后,在运行页面的源文件中可以看到如下形式的代码:
<input type="hidden" name="struts.token.name" value="user.token"/>
<input type="hidden" name="user.token" value="5J6RS8NCMZSQBH7NZDX8XRG435O2XAP"/>
第一个隐藏字段的名字是struts.token.name 是固定的,第二个影藏字段的名称是user.token 它的value的值是令牌值。
服务器首先根据stuts.token.name 找到保存令牌的请求参数名,user.token 然后获取user.token请求参数得到令牌值。
token标签必须与token,tokenSession或者 execAndWait等拦截器配合使用,这3个拦截器都能对token标签进行处理
上述3个拦截器的实现类分别为了TokenInterceprot,TokenSessionStoreInterceptor 和ExecuteAndWaitInterceptor,他们已经在Struts-default.xml文件中定义,但是没有包含在defaultStack拦截器栈中。
[b][color=red]----使用 token 拦截器---[/color][/b]
import com.opensymphony.xwork2.ActionSupport;
public class LoginAction extends ActionSupport{
private String userName;
private String userPassword;
@Override
public String execute() throws Exception {
// TODO Auto-generated method stub
return SUCCESS;
}
// get set
}
struts.xml
<?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>
<!-- 如果 注解的action配置改变时候不需要重新启动tomcate -->
<constant name="struts.devMode" value="true"/>
<constant name="struts.convention.classes.reload" value="true" />
<package name="default" extends="struts-default">
<action name="login" class="com.sh.action.LoginAction">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="token"/>
<result name="invalid.token">/login.jsp</result>
<result name="input">/login.jsp</result>
<result name="success">/index.jsp</result>
</action>
</package>
</struts>
login.jsp
<body>
<STRONG>用户登录</STRONG>
<s:form action="login">
<s:token/>
<s:actionerror/>
<s:textfield name="userName" label="姓名"/>
<s:textfield name="userPassword" label="密码"/>
<s:submit value="登录"/>
</s:form>
</body>
index.jsp
<body>
<h4>用户登录成功</h4>
登录名称:${userName}
</body>
[b][color=red]----使用 tokenSession 拦截器---[/color][/b]
tokenSession拦截器扩展了token拦截器,但是tokenSession拦截器不会返回一个特殊的结果,也不会添加一个动作错误,只是阻断后面的提交,这样做的结果就是用户将看到你同样的响应,就好像只有一次提交。
如果使用浏览器的后退功能,退回到登录页面,即使使用其他名称登录,提交表单后仍然显示原来扽牢固的名称。也就是说tokenSession拦截器只承认第一次提交。
<?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>
<!-- 如果 注解的action配置改变时候不需要重新启动tomcate -->
<constant name="struts.devMode" value="true"/>
<constant name="struts.convention.classes.reload" value="true" />
<package name="default" extends="struts-default">
<action name="login" class="com.sh.action.LoginAction">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="tokenSession"/>
<!-- 如果使用 token 下面的就有这样的一个返回值
<result name="invalid.token">/login.jsp</result> -->
<result name="input">/login.jsp</result>
<result name="success">/index.jsp</result>
</action>
</package>
</struts>
---使用 ExecAndWait拦截器
在 web应用中,往往会遇到需要执行很长时间的页面,例如上传一个很大的文件,服务器接受请求后,需要处理批量的数据。这种情况下,如果没有任何设置,那么在服务器处理的这个过程中。浏览器将呈现一片空白,这时用户不知道程序 有没有执行,很可能刷新该页面,甚至执行关闭页面的操作,因此,在等待处理的过程中,设置一个友好的信息提示页面是很有必要的。
使用execAndWait拦截器实现自动显示等待页面的具体过程如下:
(1) 当表单提交请求到来时。execAndWait拦截器将创建一个新的线程来执行Action,然后返回一个等待页面给用户,然用户只懂啊请求正在处理中。
(2) 等待页面将包含自动刷新功能,每隔几秒就通知浏览器,向初始请求的URL再次发送请求
(3) execAndWait拦截器再次截获请求,判断Action是否执行完毕,如果仍未执行完毕,则继续向用户返回等待页面;如果已经执行完毕,则向用户返回相应的执行成功页面。
execAndWait拦截器包含一下3个参数:
threadPriority:可选参数,用来指定线程的优先级。默认值为Thread.NORM_PRIORITY.
delay:可选参数,用来显示等待页面前初始的等待延迟时间,以毫秒为单位,默认情况下,没有等待延迟。
delaySleepInterval:可选参数,只能和delay参数一起使用,用来指定检查后进程是否执行完毕的时间间隔,以毫秒为单位,默认值为100毫秒。
初始等待延迟:就是可以让服务器在显示等待页面之前延迟一段时间,单位是毫秒。
在初始等待延迟时间里,execAndWait拦截器每隔100毫秒检查后台Action的执行情况,如果 Action对请求的处理,并不需要很长的时间,则等待页面不会被显示,如果Action需要较长的执行时间,则将等待页面返回给用户。
action.java login.jsp index.jsp 和上面的一样
<?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>
<!-- 如果 注解的action配置改变时候不需要重新启动tomcate -->
<constant name="struts.devMode" value="true"/>
<constant name="struts.convention.classes.reload" value="true" />
<package name="default" extends="struts-default">
<action name="login" class="com.sh.action.LoginAction">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="execAndWait">
<!-- 设置服务器处理请求的时间 ,如果超过这个时间 将显示等待页面 wait.jsp -->
<param name="delay">1000</param>
</interceptor-ref>
<result name="wait">/wait.jsp</result>
<!-- 使用 tokenSession 拦截器
<interceptor-ref name="tokenSession"/> -->
<!-- 如果使用 token 下面的就有这样的一个返回值
<interceptor-ref name="token"/>
<result name="invalid.token">/login.jsp</result> -->
<result name="input">/login.jsp</result>
<result name="success">/index.jsp</result>
</action>
</package>
</struts>
wait.jsp
<head>
<base href="<%=basePath%>">
<title>避免表单重复提交</title>
<!--设置每5s刷新 这个页面-->
<meta http-equiv="refresh" content="5,url=<s:url includeParams='all'/>">
</head>
<body>
<h4>正在登录系统<br/><br/>
请您稍候...</h4>
</body>