我遇到了双重提交问题,三天后,仍然能够确定原因。
(请注意,这不是由用户反复点击提交按钮和/或刷新页面造成的。)
FWIW,这个双重提交问题发生在一个更大的应用程序中 - 但是,我已将代码缩减为更简单的应用程序,仍然重现该问题。
用户重新创建问题的步骤是:
- 在 pageA/ControllerA - 用户在搜索字段中输入一个值(在左侧导航栏中)
- 然后用户从三个随后生成的链接中选择一个(即到 pageB/ControllerB 的链接)
- 用户在 pageB 中输入文本并点击提交按钮,这会将控制权交还给 pageA。
- 用户再次单击左侧导航栏中的搜索按钮 --- 瞧(双提交/浏览器弹出)
笔记:
- 这些页面是使用Apache Tiles构建的。
- 有为“左导航栏”生成的 HTML 代码——即,它恰好被 pageA 和 pageB 使用。
想法: 我想知道当用户单击搜索按钮时 - 两个页面共享的按钮/html(即左侧导航栏)是否为两个页面激活并导致双重提交。
如果您好奇,下面是相关代码。
瓷砖.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN" "http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>
<definition name="masterpage" template="/WEB-INF/views/masterlayout.jsp">
<put-attribute name="title" value="" type="string"/>
<put-attribute name="header" value="/WEB-INF/views/header.jsp" />
<put-attribute name="leftnav" value="/WEB-INF/views/leftnav.jsp" />
<put-attribute name="body" value="" />
<put-attribute name="footer" value="/WEB-INF/views/footer.jsp" />
</definition>
<definition name="pageA" extends="masterpage">
<put-attribute name="title" value="pageA" type="string"/>
<put-attribute name="body" value="/WEB-INF/views/bodyA.jsp"/>
<put-list-attribute name="extrastyles">
<add-attribute value="resources/css/empty.css" type="template"/>
</put-list-attribute>
<put-list-attribute name="extrascripts">
<add-attribute value="resources/js/empty.js" type="template"/>
</put-list-attribute>
</definition>
<definition name="pageB" extends="masterpage">
<put-attribute name="title" value="pageB" type="string"/>
<put-attribute name="body" value="/WEB-INF/views/bodyB.jsp"/>
<put-list-attribute name="extrastyles">
<add-attribute value="resources/css/empty.css" type="template"/>
</put-list-attribute>
<put-list-attribute name="extrascripts">
<add-attribute value="resources/js/empty.js" type="template"/>
</put-list-attribute>
</definition>
</tiles-definitions>
主布局.jsp
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><tiles:getAsString name="title" /></title>
<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/jquery-ui-1.10.3.custom/css/custom-theme/jquery-ui-1.10.3.custom.min.css" />
<tiles:useAttribute id="styleentries" name="extrastyles" classname="java.util.List" />
<c:forEach var="s" items="${styleentries}">
<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath}/${s}" /><br/>
</c:forEach>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/css/jquery-ui-1.10.3.custom/js/jquery-1.9.1.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/css/jquery-ui-1.10.3.custom/js/jquery-ui-1.10.3.custom.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/main.js"></script>
<tiles:useAttribute id="scriptentries" name="extrascripts" classname="java.util.List" />
<c:forEach var="s" items="${scriptentries}">
<script type="text/javascript" src="${pageContext.request.contextPath}/${s}"></script><br/>
</c:forEach>
</head>
<body>
<div style="display: table; min-width: 1000px; min-height: 500px;">
<div style="display: table-caption; caption-side: top; min-height: 20%; border-style: solid; border-color: black; background-color: lightyellow;">
<tiles:insertAttribute name="header" />
</div>
<div style="display: table-row; height: 60%; min-width:100%;" >
<div style="display: table-cell; max-width: 400px; min-height: 100%; border-style: solid; border-color: blue; background-color: lightblue;">
<tiles:insertAttribute name="leftnav" />
</div>
<div style="display: table-cell; min-width: 600px; min-height: 100%; border-style: solid; border-color: green; background-color: lightgray;">
<tiles:insertAttribute name="body" />
</div>
</div>
<div style="display: table-caption; caption-side: bottom; min-height: 20%; border-style: solid; border-color: black; background-color: lightyellow;">
<tiles:insertAttribute name="footer" />
</div>
</div>
</body>
</html>
头文件.jsp
<h1>header</h1>
左导航.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<div>
<h1 id="hdr1">leftnav</h1>
<c:forEach var="listObj" items="${sharedList}">
<div>
<a href="${pageContext.request.contextPath}/methodB1.html?parmvalue=${listObj.valuea}">pick ${listObj.valuea}</a>
</div>
</c:forEach>
</div>
正文A.jsp
<div>
<h1>body-A</h1>
<h4>entered: ${pojo2.stringA}</h4>
<h4>${sharedList}</h4>
</div>
bodyB.jsp
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<div>
<h1>body-B</h1>
<form:form id="form1" modelAttribute="pojo2" action="methodB2.html" method="post">
<div>
<div>
<form:label path="stringA">StringA value:</form:label>
</div>
<div>
<form:input path="stringA" size="40" maxlength="64" />
</div>
</div>
<h4>picked: ${parmvalue}</h4>
<h4>${sharedList}</h4>
<button id="submitbutton">Submit</button>
</form:form>
</div>
页脚.jsp
<h1>footer</h1>
main.js
$(document).ready(function() {
var jqXHR1 = $.ajax({
type: "GET",
url: 'constructSearchBox',
async: false
})
.done(function(data, textStatus, jqXHR)
{
if (data === "good")
{
var atdiv = "<div id='at"
+ Math.floor((Math.random()*100)+1)
+ "'>"
+ "<label id='atlabel' for='atsearchstring'></label>"
+ "<input type='search' id='atsearchstring' value=''/><br/>"
+ "<button id='atsearch' type='button'>Search</button>"
+ "</div>"
$("#hdr1").after(atdiv);
}
})
.fail(function(jqXHR, textStatus, errorThrown)
{
alert("FAIL...jqXHR=" + jqXHR + ", textStatus=" + textStatus + ", errorThrown=" + errorThrown);
});
$("#atsearch").click(function(e)
{
var searchstring = $("#atsearchstring").val();
var jqXHR2 = $.ajax({
type: "POST",
url: 'atSearch',
data: "searchstring=" + searchstring,
async: true,
cache: false
})
.done(function(data, textStatus, jqXHR)
{
location.reload(true); //window.location.href = 'atSearch';//window.location = window.location.href;//history.go(0); //
})
.fail(function(jqXHR, textStatus, errorThrown)
{
alert("FAIL...(atSearch)...jqXHR=" + jqXHR + ", textStatus=" + textStatus + ", errorThrown=" + errorThrown);
});
});
$('#submitbutton').click(function() {
$("form1").submit();
});
});
控制器A.java
package aaa.bbb.ccc;
import java.util.ArrayList;
import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.io.Serializable;
import java.util.List;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.servlet.ModelAndView;
@Controller
@Scope("session")
@SessionAttributes(
{
"sharedList"
})
public class ControllerA implements Serializable
{
@ModelAttribute("sharedList")
public List<Pojo1> createSharedList()
{
return new ArrayList<Pojo1>();
}
@RequestMapping(value = "/pageA", method = RequestMethod.GET)
@ResponseBody
public ModelAndView pageA(HttpSession session)
{
createSharedList();
return new ModelAndView("pageA"); //...construct every time - just testing...
}
@RequestMapping(value = "/constructSearchBox", method = RequestMethod.GET)
@ResponseBody
public String constructSearchBox(HttpSession session)
{
return "good"; //...construct every time - just testing...
}
@RequestMapping(value = "/atSearch", method = RequestMethod.POST)
public String atSearch(
@ModelAttribute("sharedList") List<Pojo1> sharedList,
@RequestParam(value = "searchstring", required = true) String searchstring,
HttpSession session,
Model model)
{
if (!String.valueOf(searchstring).equalsIgnoreCase(String.valueOf(session.getAttribute("lastsearchstring")))) //...same search string?...
{
try
{
sharedList.clear();
sharedList.add(new Pojo1(searchstring + "aaa", searchstring + "bbb", searchstring + "ccc"));
sharedList.add(new Pojo1(searchstring + "ddd", searchstring + "eee", searchstring + "fff"));
sharedList.add(new Pojo1(searchstring + "ggg", searchstring + "hhh", searchstring + "iii"));
session.removeAttribute("lastsearchstring");
session.setAttribute("lastsearchstring", searchstring);
}
catch (Exception e)
{
e.printStackTrace();
}
}
System.out.println("ControllerA_________________________atSearch________________________before returning...(saved) searchstring is now:" + String.valueOf(session.getAttribute("lastsearchstring")));
model.addAttribute("sharedList", sharedList);
return "pageA";
}
}
控制器B.java
package aaa.bbb.ccc;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpSession;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.bind.annotation.SessionAttributes;
@Controller
@Scope("session")
@SessionAttributes(
{
"sharedList"
})
public class ControllerB
{
public ControllerB()
{
}
@SuppressWarnings("unchecked")
@RequestMapping(value = "/methodB1", method = RequestMethod.GET)
public ModelAndView methodB1(
@RequestParam(value = "parmvalue", required = true) String parmvalue,
@ModelAttribute("pojo2") Pojo2 pojo2,
HttpSession session)
{
try
{
session.setAttribute("parmvalue", parmvalue);
return new ModelAndView("pageB");
}
catch (Exception ex)
{
ex.printStackTrace();
}
return null;
}
@SuppressWarnings("unchecked")
@RequestMapping(value = "/methodB2", method = RequestMethod.POST)
public ModelAndView methodB2(@ModelAttribute("pojo2") Pojo2 pojo2)
{
try
{
return new ModelAndView("pageA");
}
catch (Exception ex)
{
ex.printStackTrace();
}
return null;
}
}
Pojo1.java
package aaa.bbb.ccc;
public class Pojo1
{
private String valuea;
private String valueb;
private String valuec;
public Pojo1(String valuea, String valueb, String valuec)
{
this.valuea = valuea;
this.valueb = valueb;
this.valuec = valuec;
}
public String getValuea()
{
return valuea;
}
public void setValuea(String valuea)
{
this.valuea = valuea;
}
public String getValueb()
{
return valueb;
}
public void setValueb(String valueb)
{
this.valueb = valueb;
}
public String getValuec()
{
return valuec;
}
public void setValuec(String valuec)
{
this.valuec = valuec;
}
@Override
public String toString()
{
return "Pojo{" + "valuea=" + valuea + ", valueb=" + valueb + ", valuec=" + valuec + '}';
}
}
Pojo2.java
package aaa.bbb.ccc;
public class Pojo2
{
public Pojo2()
{
super();
}
public Pojo2(String stringA)
{
this.stringA = stringA;
}
private String stringA;
public String getStringA()
{
return stringA;
}
public void setStringA(String stringA)
{
this.stringA = stringA;
}
@Override
public String toString()
{
return "FormPojo{" + "stringA=" + stringA + '}';
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>aaa.bbb.ccc</groupId>
<artifactId>aaatest</artifactId>
<name>aaatest</name>
<packaging>war</packaging>
<version>1</version>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.5.10</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.5.10</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-api</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-core</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-servlet</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-template</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
</plugin>
</plugins>
<finalName>${project.artifactId}-${project.version}</finalName>
</build>
</project>