0

我遇到了双重提交问题,三天后,仍然能够确定原因。

(请注意,这不是由用户反复点击提交按钮和/或刷新页面造成的。)

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

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

  1. 在 pageA/ControllerA - 用户在搜索字段中输入一个值(在左侧导航栏中)
  2. 然后用户从三个随后生成的链接中选择一个(即到 pageB/ControllerB 的链接)
  3. 用户在 pageB 中输入文本并点击提交按钮,这会将控制权交还给 pageA。
  4. 用户再次单击左侧导航栏中的搜索按钮 --- 瞧(双提交/浏览器弹出)

笔记:

  1. 这些页面是使用Apache Tiles构建的。
  2. 有为“左导航栏”生成的 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>
4

1 回答 1

2

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

 .done(function(data, textStatus, jqXHR)
   {
                location.reload(true);
    })

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

解决方案:
更正您可以在页面 B 上提交表单时使用Post Redirect Get Pattern
。 为此,将您的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;
        }
    }
于 2013-08-07T06:59:18.053 回答