是什么导致我的双重提交问题? (现在已经三天没有进展)

时间:2013-08-07 00:52:54

标签: jsp spring-mvc tiles

我收到了双重提交问题,三天后仍然可以确定原因。

(请注意,这是 NOT 由用户反复点击提交按钮和/或刷新页面所导致的类型。)

FWIW ,这个双重提交问题发生在一个更大的应用程序中 - 但是,我已经将代码缩减为更简单的应用程序,仍然可以重现该问题。

用户重新创建问题的步骤是:

  1. 在pageA / ControllerA上 - 用户在搜索字段中输入一个值(在左侧导航栏中)
  2. 然后用户从三个随后生成的链接中选择一个(即指向pageB / ControllerB的链接)
  3. 用户在pageB中输入文本并点击提交按钮,该按钮将控制权传递回pageA。
  4. 用户再次点击左侧导航栏中的搜索按钮---瞧(双提交/浏览器弹出窗口)
  5. 注意:

    1. 这些页面使用Apache Tiles构建。
    2. 为“左侧导航栏”生成了HTML代码 - 即,恰好由pageA和pageB使用。
    3. 思想: 我想知道当用户点击搜索按钮时 - 两个页面共享的按钮/ html(即左侧导航栏)是否已激活两个页面并导致双重提交。

      如果您好奇,以下是相关代码。

      tiles.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>
      

      masterlayout.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>
      

      header.jsp中

      <h1>header</h1>
      

      leftnav.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>
      

      bodyA.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>
      

      footer.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();
          });
      });
      

      ControllerA.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";
          }
      }
      

      ControllerB.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>
      

1 个答案:

答案 0 :(得分:2)

问题: -
当用户在页面B上按提交时,表单被提交(浏览器URL变为页面“methodB2.html”)并且您将返回pageA内容以响应。 现在当用户点击生成的链接时,您将使用下面的

重新加载页面
 .done(function(data, textStatus, jqXHR)
   {
                location.reload(true);
    })

location.reload将使用当前网址重新加载页面。 在这种情况下,它是methodB.html并且reload也将提交表单,因为它是导致双重提交问题的最后一个操作。

<强>解决方案:
纠正您可以使用Post Redirect Get Pattern在页面B上提交表格。
为此,请将您的methodB2处理程序修改为

@SuppressWarnings("unchecked")
        @RequestMapping(value = "/methodB2", method = RequestMethod.POST)
        public ModelAndView methodB2(@ModelAttribute("pojo2") Pojo2 pojo2)
        {
           try
           {
              //you may have to change below pageA.jsp to whatever view pageA resolves to 
              //depending upon your viewResolver configuration

                return new ModelAndView("redirect:/pageA.jsp");
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }

            return null;
        }
    }