传统servlet对于防止表单重复提交的办法是,在提交前生成一个随机数,这个随机数将添加到request域中,同时检验要是session域中没有存这么一个随机数,则将其存入session域,否则不存。这样服务器端在收到request请求后,将其request域中的随机数取出,同时也将session域中的随机数取出。要是两者相同,则认为表单是正常提交,要是不一致则认为是重复提交。若是处理正常的表单提交时,则在提交成功后,将这个session域中的随机数清除,这样不会将下次正常的表单提交也误认为是重复提交。
struts框架对此问题进行了专门的处理,不需要开发者手动编写相关代码来生成随机数。struts提供了专门的方法来生成随机数,开发者只需要调用相关方法就可以解决这方面的问题。使用struts框架,处理表单重复提交的问题变得简单了很多。struts利用tokenProcess类的方法来完成此功能。
下面是一个例子: 它在跳转到表单提交页面前,先会会跳转到一个action上,此action负责生成一个随机数,同时将这个随机数存入到session域中,之后在跳转到表单提交的页面。struts框架利用tokenProcess类中的savaToken(request)来完成此功能。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
"http://struts.apache.org/dtds/struts-config_1_3.dtd">
<struts-config>
<form-beans>
<form-bean name="UserFormBean" type="cn.itcast.web.formbean.UserFormBean"></form-bean>
</form-beans>
<!-- 配置struts收到请求后找一个action处理 -->
<action-mappings>
<action path="/RegisterUI"
type="cn.itcast.action.RegisterUIAction"
input="/WEB-INF/jsp/Register.jsp"/>
<action path="/Register"
type="cn.itcast.action.RegisterAction"
name="UserFormBean"
scope="request"
validate="true"
input="/WEB-INF/jsp/Register.jsp"
>
<forward name="success" path="/success.jsp"></forward>
<forward name="message" path="/message.jsp"></forward>
</action>
</action-mappings>
<controller processorClass="org.apache.struts.action.RequestProcessor"></controller>
<message-resources parameter="cn.itcast.resource.MessageResource"></message-resources>
</struts-config>
RegisterUIAction用来生成一个随机数。
public class RegisterUIAction extends Action {
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
// TODO Auto-generated method stub
saveToken(request);
return mapping.getInputForward();
}
}
其中saveToken(request)方法底层的源码如下所示:
public synchronized void saveToken(HttpServletRequest request) {
HttpSession session = request.getSession();
String token = generateToken(request); //负责生成一个随机数
if (token != null) {
session.setAttribute(Globals.TRANSACTION_TOKEN_KEY, token);//将其存到
}
}
从上面的方法可以看到,generateToken负责生成一个随机数,当生成的随机数不为空时,则将其以org.apache.struts.action.TOKEN key值存入到session域中。下面是generateToken(request)方法的源码中的核心代码:
public synchronized String generateToken(String id) {
try {
long current = System.currentTimeMillis();
if (current == previous) {
current++;
}
previous = current;
byte[] now = new Long(current).toString().getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(id.getBytes());
md.update(now);
return toHex(md.digest());
} catch (NoSuchAlgorithmException e) {
return null;
}
}
当RegisterUIAction生成一个随机数后,将会跳转到表单提交页面,由于采用的是struts的html标签,其form标签将会根据session域中是否有org.apache.struts.action.TOKEN属性来生成一个隐藏表单,其意义是,当存在这个属性后,证明要防表单重复提交。这是form标签会自动生成一个<input type="hiden"/>的标签,其存入的值为此session与中存入的随机数,将用来与session中的值进行比较,要是一致,则认为是正常表单提交,否则认为是重复提交,下面是<html:form>标签中生成此隐藏标签的源代码:
results.append(this.renderToken());
protected String renderToken() {
StringBuffer results = new StringBuffer();
HttpSession session = pageContext.getSession();
if (session != null) {
String token =
(String) session.getAttribute(Globals.TRANSACTION_TOKEN_KEY);
if (token != null) {//要是存在,则生成一个隐藏字段
results.append("<div><input type=\"hidden\" name=\"");
results.append(Constants.TOKEN_KEY);
results.append("\" value=\"");
results.append(token);
if (this.isXhtml()) {
results.append("\" />");
} else {
results.append("\">");
}
results.append("</div>");
}
}
return results.toString();
}
从上面代码可以看出,它将会以org.apache.struts.taglib.html.TOKEN为属性名,以session中的token值为值,生成一个隐藏字段.这个字段将会在处理注册页面时取出并与session域中的存入的值进行校验,要是相等,则认为是有效提交,之后需要将此session域中的org.apache.struts.taglib.html.TOKEN删除,这样在继续刷新页面时,session域中将不会存在此域,从而爆出重复提交。删除指定session属性,TokenProces类也有一个函数完成此功能。如下所示:resetToken(request);
其源码如下:
public synchronized void resetToken(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(Globals.TRANSACTION_TOKEN_KEY);
}
下面是处理registerAction源码:
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
// TODO Auto-generated method stub
UserFormBean ufb = (UserFormBean)form;
ufb.setUsername("zhangshan");
ufb.setCity("beijing");
ufb.setGender("male");
if(isTokenValid(request)){
request.setAttribute("message", "注册成功!!!");
resetToken(request); //删除session域中的随机数
}else{
request.setAttribute("message", "不能重复提交!!!");
}
return mapping.getInputForward();
}