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);
}
}
0、 使用注意:usermesg.jsp中的form表單必須使用struts的標籤<html:form>。
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 將不會起到避免重複提交的作用