1、你想发贴时,点击“我要发贴”链接的代码可以里这样的:
〈html:link action="subject.do?method=add"〉我要发贴〈/html:link〉
subject.do 和 method 这些在struct-config.xml如何定义我就不说了,点击链接后,会执行subject.do的add方法,代码如上面说的,跳转到subjectAdd.jsp页面。页面的代码大概如下:
〈html:form action="subjectForm.do?method=insert"〉
〈html:text property="title" /〉
〈html:textarea property="content" /〉
〈html:submit property="发表" /〉
〈html:reset property="重填" /〉
〈html:form〉
如果你在add方法里加了“saveToken(request);”这一句,那在subjectAdd.jsp生成的页面上,会多一个隐藏字段,类似于这样〈input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae"〉,2、点击发表后,表单提交到subjectForm.do里的insert方法后,你在insert方法里要将表单的数据插入到数据库中,如果没有进行重复提交的控制,那么每点击一次浏览器的刷新按钮,都会在数据库中插入一条相同的记录,增加下面的代码,你就可以控制用户的重复提交了。
if (isTokenValid(request, true)) {
// 表单不是重复提交
//这里是保存数据的代码
} else {
//表单重复提交
saveToken(request);
//其它的处理代码
}
注意,你必须在add方法里使用了saveToken(request),你才能在insert里判断,否则,你每次保存操作都是重复提交。
记住一点,Struts在你每次访问Action的时候,都会产生一个令牌,保存在你的Session里面,如果你在Action里的函数里面,使用了saveToken(request);,那么这个令牌也会保存在这个Action所Forward到的jsp所生成的静态页面里。
如果你在你Action的方法里使用了isTokenValid,那么Struts会将你从你的request里面去获取这个令牌值,然后和Session里的令牌值做比较,如果两者相等,就不是重复提交,如果不相等,就是重复提交了。
由于我们项目的所有Action都是继承自BaseDispatchAction这个类,所以我们基本上都是在这个类里面做了表单重复提交的控制,默认是控制add方法和insert方法,如果需要控制其它的方法,就自己手动写上面这些代码,否则是不需要手写的,控制的代码如下:
public abstract class BaseDispatchAction extends BaseAction {
protected ActionForward perform(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
String parameter = mapping.getParameter();
String name = request.getParameter(parameter);
if (null == name) { //如果没有指定 method ,则默认为 list
name = "list";
}
if ("add".equals(name)) {
if ("add".equals(name)) {
saveToken(request);
}
} else if ("insert".equals(name)) {
if (!isTokenValid(request, true)) {
resetToken(request);
saveError(request, new ActionMessage("error.repeatSubmit"));
log.error("重复提交!");
return mapping.findForward("error");
}
}
return dispatchMethod2(mapping, form, request, response, name);
}
}
注意:表单必须是struts标签!
原理:
1、在session中放入同步令牌。原理:调用TokenProcessor类的saveToken(HttpServletRequest arg0);源码如下:
在sesssion中放入名称为Globale.TRANSACTION_TOKEN_KEY的同步令牌:token
public synchronized void saveToken(HttpServletRequest request)...{
HttpSession session=request.getSession();
String token=generateToken(request);
if(token!=null)...{
session.setAttribute(Globale.TRANSACTION_TOKEN_KEY,token);
}
}
2、在打开usermesg.jsp时,应用服务器遇到<html:form>时,便会调用FormTag类的renderToken()方法创建hidden元素。源码如下:
protected String renderToken()...{
StringBuffer result=new StringBuffer();
HttpSession session=pageContext.getSession();
if(session!=null)...{
String token=(String)session.getAttribute(Globale.TRANSACTION_TOKEN_KEY);
if(token!=null)...{
result.append("<input type="hidden" name="");
result.append(Constants.TOKEN_KEY);
result.append("" value="");
result.append(token);
if(this.isXhtml)...{
result.append("" />");
}else...{
result.append("" >");
}
}
}
return result.toStriong();
}
hidden元素Constants.TOKEN_KEY的值就是session中的名称为Globale.TRANSACTION_TOKEN_KEY的同步令牌值
3、验证同步令牌,原理:调用TokenProcessor的isTokenValid(HttpServletRequest arg0,boolean arg1)源码如下:
public synchronized boolean isTokenValid(HttpServletRequest request,boolean reset)...{
HttpSession session request.getSession(false);
if(session==null) return false;
String saved=(String)session.getAttribute(Globale.TRANSACTION_TOKEN_KEY);
if(saved==null) return false;
if(reset) this.resetToken(request);
String token=request.getParameter(Constants.TOKEN_KEY);
if(token==null) return false;
return saved.equals(token);
}
从叶面取出同步令牌值和session中的进行比较。如果相等则为第一次,不等就是重复提交。前提就是传进来的参数reset必须是true。不然每次都相同。其中resetToken()方法如下:
public synchronized void resetToken(HttpServletRequest request)...{
HttpSession session request.getSession(false);
if(session==null)...{
return;
}
session.removeAttribute(Globale.TRANSACTION_TOKEN_KEY);
}
这就是在isTokenValid方法中boolean参数的作用:清除session中的同步令牌,避免重复提交。如果把true改为false 将不会起到避免重复提交的作用
另外:
建一个action基类TokenAction,使用模板方法,如下:
public ActionForward execute( ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
ActionForward forward = null;
if (isTokenValid(request, true)) {
forward = doExecute(mapping, form, request, response);
} else {
saveToken(request);
throw new TokenRepeatException(MessageLocator.getMessage("error.token"));
}
return forward;
}
public abstract ActionForward doExecute( ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception;
注:TokenRepeatException是自定义异常,它的父类ApplicationException在struts-config.xml中被声明,以便于action捕获。如下(我的错误消息资源是用spring来配的,没有用struts的):
<struts-config>
<global-exceptions>
<exception handler="com.chage.framework.struts.BusinessExceptionHandler" key="todo" type="com.chage.framework.exception.ApplicationException" />
</global-exceptions>
.....
</struts-config>
然后,观察这个异常处理类:
public class BusinessExceptionHandler extends ExceptionHandler {
public ActionForward execute(Exception exception,
ExceptionConfig config,
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws ServletException {
request.setAttribute("exception", exception);
if (exception instanceof TokenRepeatException) {
return mapping.findForward("tokenRepeat");
} else
//display error info in action come from
return mapping.getInputForward();
}
}
有几个注意事项,记录备案:
1.关于异常信息哪里显示: 异常显示当然不能在表单页面上了,比如说,你成功更新了一条记录后,返回list的页面,当用户回退重复提交,再返回这个list页并显示提示信息是比较合理的,最好是定义另外一个forward,我统一转向forward名称为 “tokenRepeat“,每个需要控制重复提交的action中必须定义它。最后在jsp页上加上异常显示:
<logic:present name="exception">
<font color=red> <bean:write name="exception" property="message"/></font><br>
</logic:present>
2.你必须通过action来转发而不是直接进入jsp页,因为你得调用saveToken方法来记录令牌字。
Token在struts中使用详解
最新推荐文章于 2020-04-25 15:55:35 发布