此方法只适合.单方法(SimpleFormController ).不适合多方法
public class BaseFormController extends SimpleFormController {
/**
* 防止多次提交
*
* @param request
* @param response
* @return
* @throws Exception
*/
protected ModelAndView disallowDuplicateFormSubmission(HttpServletRequest request, HttpServletResponse response) throws Exception {
BindException errors = new BindException(formBackingObject(request), getCommandName());
errors.reject("duplicateFormSubmission", null, "对不起,你不能重复提交同一表单内容!");
return showForm(request, response, errors);
}
/*
* (non-Javadoc)
*
* @see org.springframework.web.servlet.mvc.AbstractFormController#handleInvalidSubmit(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response) throws Exception {
return disallowDuplicateFormSubmission(request, response);
}
}
关于表单防重复提交一些东东 .
分类: Java相关 2009-03-13 22:33 2122人阅读 评论(3) 收藏 举报
前阵子弄了些表单防重复提交的东西,想整理整理,免得下次要用时再四处去找,其实这里的东西还是挺简单的。
原理:
在Session中保存一个表单的唯一编号,将该编号放在一个隐藏域中,同其他数据一同提交。在提交表单后,通过拦截器或其他机制检查唯一编号,如果存在则说明表单是第一次提交,如果不存在则被重复提交(理由很简单,在第一次提交检查后就会从Session中移除该编号)。保存编号可以用一个 HashMap。
上代码:
表单类,用于保存表单创建时间和表单的标示
[java] view plaincopyprint?
01.package form;
02.import java.io.Serializable;
03.import java.util.Date;
04.import java.util.HashMap;
05.import java.util.Map;
06.import org.apache.commons.lang.builder.ToStringBuilder;
07./**
08. * 表单类
09. *
10. * @author DigitalSonic
11. */
12.public class Form implements Serializable {
13. /**
14. * serialVersionUID
15. */
16. private static final long serialVersionUID = 8796758608626021150L;
17. public static final String FORM_UNIQ_ID_FIELD_NAME = "_form_uniq_id";
18. /** 表单标识*/
19. private String token;
20. /** 表单创建时间*/
21. private Date createTime;
22. /**
23. * 构造函数
24. */
25. public Form(String token) {
26. this.token = token;
27. this.createTime = new Date();
28. }
29. public String toString() {
30. return ToStringBuilder.reflectionToString(this);
31. }
32. public String getToken() {
33. return token;
34. }
35. public Date getCreateTime() {
36. return createTime;
37. }
38.}
package form;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.builder.ToStringBuilder;
/**
* 表单类
*
* @author DigitalSonic
*/
public class Form implements Serializable {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 8796758608626021150L;
public static final String FORM_UNIQ_ID_FIELD_NAME = "_form_uniq_id";
/** 表单标识*/
private String token;
/** 表单创建时间*/
private Date createTime;
/**
* 构造函数
*/
public Form(String token) {
this.token = token;
this.createTime = new Date();
}
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
public String getToken() {
return token;
}
public Date getCreateTime() {
return createTime;
}
}
表单管理器接口
[java] view plaincopyprint?
01.package form;
02.import javax.servlet.http.HttpServletRequest;
03./**
04. * 表单管理器,负责管理Session中的表单。
05. *
06. * @author DigitalSonic
07. */
08.public interface FormManager {
09. /**
10. * 生成一个新的表单
11. */
12. public Form newForm(HttpServletRequest request);
13. /**
14. * 判断表单是否存在。
15. */
16. public boolean hasForm(HttpServletRequest request, String token);
17. /**
18. * 访问参数中是否存在表单Token。
19. */
20. public boolean hasFormToken(HttpServletRequest request);
21. /**
22. * 销毁一个表单
23. */
24. public void destroyToken(HttpServletRequest request, String token);
25. /**
26. * 打印表单信息。
27. */
28. public String dumpForm(HttpServletRequest request, String token);
29.}
package form;
import javax.servlet.http.HttpServletRequest;
/**
* 表单管理器,负责管理Session中的表单。
*
* @author DigitalSonic
*/
public interface FormManager {
/**
* 生成一个新的表单
*/
public Form newForm(HttpServletRequest request);
/**
* 判断表单是否存在。
*/
public boolean hasForm(HttpServletRequest request, String token);
/**
* 访问参数中是否存在表单Token。
*/
public boolean hasFormToken(HttpServletRequest request);
/**
* 销毁一个表单
*/
public void destroyToken(HttpServletRequest request, String token);
/**
* 打印表单信息。
*/
public String dumpForm(HttpServletRequest request, String token);
}
表单管理器接口实现
[java] view plaincopyprint?
01.package form;
02.import java.util.ArrayList;
03.import java.util.HashMap;
04.import java.util.List;
05.import java.util.Map;
06.import javax.servlet.http.HttpServletRequest;
07.import javax.servlet.http.HttpSession;
08.import org.apache.commons.lang.RandomStringUtils;
09.import org.apache.commons.lang.StringUtils
10./**
11. * 表单管理器实现类
12. *
13. * @author DigitalSonic
14. */
15.public class FormManagerImpl implements FormManager {
16. private static final String SESSION_KEY_OF_FROMS = "_forms_in_session";
17. /** 表单最大个数 */
18. private int maxFormNum = 7;
19.
20. /**
21. * 销毁一个表单
22. */
23. public void destroyToken(HttpServletRequest request, String token) {
24. getForms(request).remove(token);
25. }
26. /**
27. * 打印表单信息。
28. */
29. public String dumpForm(HttpServletRequest request, String token) {
30. Form form = getForms(request).get(token);
31. if (form == null) {
32. return "null";
33. }
34. return form.toString();
35. }
36. /**
37. * 判断表单是否存在。如果token为null,直接返回false。
38. *
39. * @see #getForms(HttpServletRequest)
40. */
41. public boolean hasForm(HttpServletRequest request, String token) {
42. if (token == null) {
43. return false;
44. }
45. return getForms(request).containsKey(token);
46. }
47.
48. /**
49. * 访问参数中是否存在表单Token。
50. */
51. public boolean hasFormToken(HttpServletRequest request) {
52. String formToken = request.getParameter(Form.FORM_TOKEN_FIELD_NAME);
53. return StringUtils.isNotBlank(formToken);
54. }
55. /**
56. * 生成一个新的表单,如果目前表单个数大于设定的最大表单数则先删除最早的一个表单。<br>
57. * 新表单用RandomStringUtils.randomAlphanumeric(32)生成Token。
58. *
59. * @return 创建的新表单
60. * @see #removeOldestForm(HttpServletRequest)
61. * @see org.apache.commons.lang.RandomStringUtils#random(int)
62. */
63. public Form newForm(HttpServletRequest request) {
64. Form form = new Form(RandomStringUtils.randomAlphanumeric(32));
65. Map<String, Form> forms = getForms(request);
66. synchronized (forms) {
67. // 如果目前表单个数大于等于最大表单数,那么删除最老的表单,添加新表单。
68. if (forms.size() >= maxFormNum) {
69. removeOldestForm(request);
70. }
71. forms.put(form.getToken(), form);
72. }
73. return form;
74. }
75. /**
76. * 获得目前session中的表单列表。
77. *
78. * @return 返回的Map中以表单的token为键,Form对象为值
79. */
80. @SuppressWarnings("unchecked")
81. protected Map<String, Form> getForms(HttpServletRequest request) {
82. Map<String, Form> formsInSession = null;
83. HttpSession session = request.getSession();
84. synchronized (session) {
85. formsInSession = (Map<String, Form>) session.getAttribute(SESSION_KEY_OF_FROMS);
86. if (formsInSession == null) {
87. formsInSession = new HashMap<String, Form>();
88. session.setAttribute(SESSION_KEY_OF_FROMS, formsInSession);
89. }
90. }
91. return formsInSession;
92. }
93. /**
94. * 删除最老的Form
95. *
96. * @see #destroyToken(HttpServletRequest, String)
97. */
98. protected void removeOldestForm(HttpServletRequest request) {
99. List<Form> forms = new ArrayList<Form>(getForms(request).values());
100. if (!forms.isEmpty()) {
101. Form oldestForm = forms.get(0);
102. for (Form form : forms) {
103. if (form.getCreateTime().before(oldestForm.getCreateTime())) {
104. oldestForm = form;
105. }
106. }
107. destroyToken(request, oldestForm.getToken());
108. }
109. }
110. public void setMaxFormNum(int maxFormNum) {
111. this.maxFormNum = maxFormNum;
112. }
113.}
package form;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils
/**
* 表单管理器实现类
*
* @author DigitalSonic
*/
public class FormManagerImpl implements FormManager {
private static final String SESSION_KEY_OF_FROMS = "_forms_in_session";
/** 表单最大个数 */
private int maxFormNum = 7;
/**
* 销毁一个表单
*/
public void destroyToken(HttpServletRequest request, String token) {
getForms(request).remove(token);
}
/**
* 打印表单信息。
*/
public String dumpForm(HttpServletRequest request, String token) {
Form form = getForms(request).get(token);
if (form == null) {
return "null";
}
return form.toString();
}
/**
* 判断表单是否存在。如果token为null,直接返回false。
*
* @see #getForms(HttpServletRequest)
*/
public boolean hasForm(HttpServletRequest request, String token) {
if (token == null) {
return false;
}
return getForms(request).containsKey(token);
}
/**
* 访问参数中是否存在表单Token。
*/
public boolean hasFormToken(HttpServletRequest request) {
String formToken = request.getParameter(Form.FORM_TOKEN_FIELD_NAME);
return StringUtils.isNotBlank(formToken);
}
/**
* 生成一个新的表单,如果目前表单个数大于设定的最大表单数则先删除最早的一个表单。<br>
* 新表单用RandomStringUtils.randomAlphanumeric(32)生成Token。
*
* @return 创建的新表单
* @see #removeOldestForm(HttpServletRequest)
* @see org.apache.commons.lang.RandomStringUtils#random(int)
*/
public Form newForm(HttpServletRequest request) {
Form form = new Form(RandomStringUtils.randomAlphanumeric(32));
Map<String, Form> forms = getForms(request);
synchronized (forms) {
// 如果目前表单个数大于等于最大表单数,那么删除最老的表单,添加新表单。
if (forms.size() >= maxFormNum) {
removeOldestForm(request);
}
forms.put(form.getToken(), form);
}
return form;
}
/**
* 获得目前session中的表单列表。
*
* @return 返回的Map中以表单的token为键,Form对象为值
*/
@SuppressWarnings("unchecked")
protected Map<String, Form> getForms(HttpServletRequest request) {
Map<String, Form> formsInSession = null;
HttpSession session = request.getSession();
synchronized (session) {
formsInSession = (Map<String, Form>) session.getAttribute(SESSION_KEY_OF_FROMS);
if (formsInSession == null) {
formsInSession = new HashMap<String, Form>();
session.setAttribute(SESSION_KEY_OF_FROMS, formsInSession);
}
}
return formsInSession;
}
/**
* 删除最老的Form
*
* @see #destroyToken(HttpServletRequest, String)
*/
protected void removeOldestForm(HttpServletRequest request) {
List<Form> forms = new ArrayList<Form>(getForms(request).values());
if (!forms.isEmpty()) {
Form oldestForm = forms.get(0);
for (Form form : forms) {
if (form.getCreateTime().before(oldestForm.getCreateTime())) {
oldestForm = form;
}
}
destroyToken(request, oldestForm.getToken());
}
}
public void setMaxFormNum(int maxFormNum) {
this.maxFormNum = maxFormNum;
}
}
Spring中的拦截器实现
[java] view plaincopyprint?
01.package form;
02.import javax.servlet.http.HttpServletRequest;
03.import javax.servlet.http.HttpServletResponse;
04.import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
05./**
06. * 禁止表单重复提交拦截器
07. *
08. * @author DigitalSonic
09. */
10.public class DenyDuplicateFormSubmitInterceptor extends HandlerInterceptorAdapter {
11. private FormManager formManager;
12.
13. public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
14. Object handler) throws Exception {
15. boolean flag = true;
16. String token = request.getParameter(Form.FORM_UNIQ_ID_FIELD_NAME);
17. if (token != null) {
18. if (formManager.hasForm(request, token)) {
19. formManager.destroyToken(request, token);
20. } else {
21. flag = false;
22. throw new Exception("表单重复提交或过期,令牌[" + token + "]");
23. }
24. }
25. return flag;
26. }
27. public void setFormManager(FormManager formManager) {
28. this.formManager = formManager;
29. }
30.}
package form;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
* 禁止表单重复提交拦截器
*
* @author DigitalSonic
*/
public class DenyDuplicateFormSubmitInterceptor extends HandlerInterceptorAdapter {
private FormManager formManager;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
boolean flag = true;
String token = request.getParameter(Form.FORM_UNIQ_ID_FIELD_NAME);
if (token != null) {
if (formManager.hasForm(request, token)) {
formManager.destroyToken(request, token);
} else {
flag = false;
throw new Exception("表单重复提交或过期,令牌[" + token + "]");
}
}
return flag;
}
public void setFormManager(FormManager formManager) {
this.formManager = formManager;
}
}
在Spring MVC的HandlerMapping中配置该拦截器,随后在需要表单验证的Controller里做如下修改:
1、注入FormManager实例,主要是用newForm()生成一个新的Form对象
2、在返回的ModelAndView里加入该Form对象,假设名称是form
3、页面的表单中加入如下隐藏域
<input type="hidden" value="${form.token}" name="_form_uniq_id" />
<input type="hidden" value="${form.token}" name="_form_uniq_id" />
代码中我去掉了些东西,应该还是能正常工作的,反正原理就是这么回事,呵呵。
抱歉打扰一下,我想说的是:
第二.你不必加同步锁,Session每个线程各有一份
第三.Map的value目的只是为了提供date,好替除最久未使用的token,但你不必for循环每一项吧,token每次put到map是经过你的代码的对吗?那么你很轻松就可记录下put的顺序。还好这个map不太大,但是你的设计可以更好的啊。
建议:把map打散,LinkedList记录顺序,HashSet快速contain到key。
TreeMap按date排序,基本上能解决这个问题
首先,需要将继承了SimpleFormController之类的sessionForm设为true。这样,在显示一个新表单时,Spring会将command存放在session中,而在提交表单时,Spring会从session中取出此command,随后立即从session中删除存放command的attribute。如果发现在session中没有command,Spring将其断定为重复提交,转而执行handleInvalidSubmit(request, response),可覆盖此方法负责防止重复提交的任务。可以这么说,当setSessionForm(true)之后,如果没有先后经历显示表单、提交表单的过程,就会被认为是重复提交表单。而有一些情况下却必须重复提交表单,如,修改数据库的数据后,试图写入数据库时因某些异常失败,如果此时异常被当前页面捕获并依旧返回当前页面,由于command已经被Spring在后台从session中移走,因此,就被认为是无效重复提交,从而导致第二次经修改后的记录无法正确提交到数据库中。handleInvalidSubmit()必须考虑到这种情况。 ------------------------------------------------------------------------------------------------- 方法2 楼上提供的方法比较麻烦。 |