一、什么情况下会重复提交表单
- 在网络有延迟的情况下,用户多次点击提交按钮
- 用户提交数据成功后,点击“刷新”按钮,导致表单重复提交
- 用户提交表单后,“后退”到表单页面,再次提交,导致表单的重复提交
二、防止表单重复提交的方法
使用到的表单:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>表单重复提交解决方法</title>
</head>
<body>
<!-- 将表单的数据提交到FormServlet中 -->
<form action="${pageContext.request.contextPath}/servlet/FormServlet" method="post">
用户名:<input type="text" name="username">
<input type="submit" value="提交" id="submit">
</form>
</body>
</html>
servlet代码:
package xdp.gacl.session;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class FormServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//设置编码格式,防止产生乱码
request.setCharacterEncoding("UTF-8");
String userName = request.getParameter("username");
try {
//睡眠3秒,模拟网络延迟
Thread.sleep(3*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印得到的数据
System.out.println(userName);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
1、使用javaScript防止表单重复提交
具体的思路:在用户点击提交后,禁止再次点击“提交”按钮。
第一种实现方式(给表单添加事件处理):
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>Form表单</title>
<script type="text/javascript">
//定义变量,表示调单是否可以提交
var isCom = false;
function dosubmit(){
// 判断当前的isCom是否可以提交
if(isCom==false){
// 修改变量,表示表单已经提交
isCom = true;
return true;
}else{
return false;
}
}
</script>
</head>
<body>
<form action="${pageContext.request.contextPath}/servlet/FormServlet" onsubmit="return dosubmit()" method="post">
用户名:<input type="text" name="username">
<input type="submit" value="提交" id="submit">
</form>
</body>
</html>
第二种实现方式(提交后,将表单设置为不可用):
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
<title>Form表单</title>
<script type="text/javascript">
function dosubmit(){
//获取表单提交按钮
var btnSubmit = document.getElementById("submit");
//将表单提交按钮设置为不可用,
btnSubmit.disabled= "disabled";
}
}
</script>
</head>
<body>
<form action="${pageContext.request.contextPath}/servlet/FormServlet" method="post">
用户名:<input type="text" name="username">
<input type="submit" value="提交" id="submit" onsubmit="dosubmit()">
</form>
</body>
</html>
很明显,使用javaScript的方式来处理表单重复提交,是可以的。但是这种方式只能处理第一种场景的表单重复提交,对于第二、三种场景的表单重复提交是处理不了的。这时就可以使用session来解决后面两种场景的表单重复提交。
2、session处理表单重复提交
具体思路:在服务器端产生一个唯一标志符,将这个唯一标识符隐藏在表单中,表单提交时,判断两者是否相同,来进行相应的处理。这个唯一标识符又叫令牌。
什么情况下表示表单不是第一次提交:
- 表单中令牌个和服务器中令牌不一致
- 表单中的令牌在服务器中不存在
表单中没有令牌
创建令牌的类:
// 单例模式
class Token{
private static Token instance = new Token();
private Token(){}
public static Token getInstance(){
return instance;
}
public static String getToken(){
String token = System.currentTimeMillis()+new Random().nextInt()+"";
// 使用MessageDigest 类 产生一个唯一随机数
try {
// 使用md5算法产生随机数
MessageDigest md = MessageDigest.getInstance("md5");
// 使用token数字,产生一个唯一的随机数
byte[] md5 = md.digest(token.getBytes());
// 在使用base64编码将随机数的位数固定
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(md5);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
得到一个令牌,存放到session域对象中,跳转到jsp页面:
package test.session;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import sun.misc.BASE64Encoder;
/**
* Servlet implementation class SessionDemo3
*
* SessionDemo3和4 一起实现 防止表单的重复提交
* 这个负责产生随机数,发送给表单
*/
@WebServlet("/SessionDemo3")
public class SessionDemo3 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 产生随机数
String token = Token.getInstance().getToken();
// 将令牌存到session中
HttpSession session = request.getSession();
session.setAttribute("token", token);
// 转发到 显示 表单页面
request.getRequestDispatcher("/Session34.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
表单界面:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB18030">
<title>Insert title here</title>
</head>
<body>
<!-- 将表单提交给SessionDemo4进行处理 -->
<form action="/javaWeb/SessionDemo4" method="post">
<!-- 隐藏的令牌 -->
<input type="hidden" name="token" value="${token }">
名字:<input type="text" name="name"> <br>
<input type="submit" value="注册">
</form>
</body>
</html>
表单的处理页面:
package test.session;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* Servlet implementation class SessionDemo4
*
* 这个页面负责 处理表单发的请求
*
*/
@WebServlet("/SessionDemo4")
public class SessionDemo4 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
boolean b = isToken(request);
if(!b){
response.getOutputStream().write("请不要重复提交".getBytes());
System.out.println("请不要重复提交");
return ;
}
// 将token从服务器端删除
request.getSession().removeAttribute("token");
System.out.println("注册成功");
response.getOutputStream().write("注册成功".getBytes());
}
// 将表单中 token 和 服务器的session进行对比
private boolean isToken(HttpServletRequest request) {
// 获取表单的token
String token = request.getParameter("token");
if(token==null){
return false;
}
// 获取已经创建的session
HttpSession s = request.getSession(false);
if(s==null){
return false;
}
// 获取session域对象中的令牌
String ss = (String) s.getAttribute("token");
if(ss==null){
return false;
}
// 判断表单的token是否和session的tokrn相同
if(!token.equals(ss)){
return false;
}
return true;
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
这样就能处理场景二、三的表单重复提交。在实际开发中一般都是要结合javaScript和session一起来防止所有的场景的表单提交。