当用户在表单中填写完信息,单击“提交”按钮后,可能会因为没有看到成功信息而再次单击“提交”按钮,从而导致在服务端接收到两条同样的信息,如果这个信息是要保存到数据库里的,那么就会出现两条相同的信息,而这往往往会引起数据库异常,对整个系统的稳定运行会产生致命的危害。在实际应用中,由于用户没有及时看到响应信息而导致的重复提交时有发生。响应不及时有可能是因为这个时段服务器的负载较大,又或者这个处理本身就是比较耗时的操作。
有时候,即使响应及时,也有可能会出现重复提交的情况。服务器端的程序在处理完用户提交的信息后,调用了RequestDispatcher.forward()方法将用户的请求转发给成功页面,用户看到成功信息后,单了浏览器的“刷新”按钮,此时浏览器会再次提交用户先前输入的数据,这是因为调用了RequestDispatcher.forward()方法,浏览器所保留的URL是先前表单提交的URL,如果是采用了RequestDispatcher.sendRedircert()方法将客户端重定向到成功页面,就不会出现重复提交的问题了。
下面用客户端与服务器端令牌相结合的方式,防止用户重复提交表单。
废话少说,出代码
本示例项目文件结构如下图:

login.jsp页面代码如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ page import="com.test.TokenProcessor" %>
<%@ page contentType="text/html; charset=UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>防止表单重复提交</title>
<script type="text/javascript">
<!--
var checkSubmitFlg=true;
function checkSubmit(){
if(true==checkSubmitFlg){
document.theForm.btnSubmit.disable=true;
document.theForm.submit();
checkSubmitFlg=false;
}else{
alert("你已经提交 了表单,请不要重复提交!");
}
}
//-->
</script>
</head>
<body>
<%
TokenProcessor processor=TokenProcessor.getInstance();
String token=processor.getToken(request);
%>
<form action="handler" name="theForm" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"/></td>
</tr>
<tr>
<td>邮件地址:</td>
<td>
<input type="text" name="email"/>
<input type="hidden" name="ltai701" value="<%=token %>"/>
</td>
</tr>
<tr>
<td><input type="reset" value="重填"/></td>
<td><input type="button" value="提交" name="btnSubmit" onclick="checkSubmit()"/></td>
</tr>
</table>
</form>
</body>
</html>
HandlerServlet代码如下:
package com.test;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HandlerServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
int count=0;
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out=resp.getWriter();
TokenProcessor processor=TokenProcessor.getInstance();
if(processor.isTokenValid(req)){
System.out.println("submit:"+count);
if(count%2==1) count=0;
else count++;
out.println("success");
}else{
processor.saveToken(req);
out.println("你已经提交了表单,同一表单不能两次提交");
}
out.close();
}
}
TokenProcessor代码如下:
package com.test;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class TokenProcessor {
static final String TOKEN_KEY="ltai701";
private static TokenProcessor instance=new TokenProcessor();
public static TokenProcessor getInstance(){
return instance;
}
private long previous;
public synchronized boolean isTokenValid(HttpServletRequest request){
//得到请求的当前session对象
HttpSession session=request.getSession(false);
if(session==null){
return false;
}
//从session中取出保存的令牌值
String saved=(String)session.getAttribute(TOKEN_KEY);
if(saved==null){
return false;
}
//清除session中的令牌值
resetToken(request);
//得到请求参数中的令牌值
String token=request.getParameter(TOKEN_KEY);
if(token==null){
return false;
}
return saved.equals(token);
}
public synchronized void resetToken(HttpServletRequest request){
HttpSession session=request.getSession(false);
if(session==null){
return;
}
session.removeAttribute(TOKEN_KEY);
}
public synchronized void saveToken(HttpServletRequest request){
HttpSession session=request.getSession(false);
String token=generateToken(request);
if(token!=null){
session.setAttribute(TOKEN_KEY, token);
}
}
public synchronized String generateToken(HttpServletRequest request){
HttpSession session =request.getSession(false);
try{
byte id[]=session.getId().getBytes();
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);
md.update(now);
return toHex(md.digest());
}catch (NoSuchAlgorithmException e) {
// TODO: handle exception
e.printStackTrace();
return null;
}
}
private String toHex(byte buffer[]){
StringBuffer sb=new StringBuffer(buffer.length*2);
for(int i=0;i<buffer.length;i++){
sb.append(Character.forDigit((buffer[i]&0xf0)>>4, 16));
sb.append(Character.forDigit(buffer[i]&0x0f, 16));
}
return sb.toString();
}
public synchronized String getToken(HttpServletRequest request){
HttpSession session=request.getSession(false);
if(null==session)
return null;
String token=(String)session.getAttribute(TOKEN_KEY);
if(null==token){
token=generateToken(request);
if(token!=null){
session.setAttribute(TOKEN_KEY,token);
return token;
}else
return null;
}else
return token;
}
}
web.xml配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>HanderServlet</servlet-name>
<servlet-class>com.test.HandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HanderServlet</servlet-name>
<url-pattern>/handler</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
</web-app>
运行效果图如下:
等待服务器端响应时重复按“提交”按钮
提交完成后再刷新浏览器或者按回退键再按前进键,则有
希望此文章能帮助到有需要的人,多谢。