翻译于 2014/04/09 17:31
6 人 顶 此译文
Spring MVC 3.1 version has added a very useful feature Flash attribute which it lacked and is now solving a long time problem of POST/Redirect/GET pattern.
In a normal Web based MVC application each form submitted POST the data to the server. A normal spring controller (tagged with annotation @Controller) fetches the data from request and process it further (save or update in database). Once the operation is successful, user is forwarded to page showing success of operation. Traditionally if we handle this via POST/Forward/GET, then it may cause sometime multiple form submission issue. User might press F5 and the same data is posted again.
To overcome this problem, POST/Redirect/GET pattern is used in MVC applcations. Once user form is posted successfully, we redirect the request to another success page. This causes browser to perform a new GET request and load the page. Thus is user presses F5, the GET request gets loaded instead of submitting form again.
Image credit: Wikipedia
Spring MVC 3.1版本加了一个很有用的特性,Flash属性,它能解决一个长久以来缺少解决的问题,一个POST/Redirect/GET模式问题。
正常的MVC Web应用程序在每次提交都会POST数据到服务器。一个正常的Controller (被注解 @Controller标记)从请求获取数据和处理它 (保存或更新数据库)。一旦操作成功,用户就会被带到(forward)一个操作成功的页面。传统上来说,这样的POST/Forward/GET模式,有时候会导致多次提交问题. 例如用户按F5刷新页面,这时同样的数据会再提交一次。
为了解决这问题, POST/Redirect/GET 模式被用在MVC应用程序上. 一旦用户表单被提交成功, 我们重定向(Redirect)请求到另一个成功页面。这样能够令浏览器创建新的GET请求和加载新页面。这样用户按下F5,是直接GET请求而不是再提交一次表单。
Image credit: Wikipedia
While this approach looks perfect and solve the problem of multiple form submission, it add one more issue of retrieving request parameters and attributes. Normally when we generate an http redirect request, the data stored in request is lost making it impossible for next GET request to access some of the useful information from request.
Flash attributes comes handy in such cases. Flash attributes provide a way for one request to store attributes intended for use in another. Flash attributes are saved temporarily (typically in the session) before the redirect to be made available to the request after the redirect and removed immediately.
In order to do this, Flash feature uses two collections. FlashMap
is used to hold flash attributes while FlashMapManager
is used to store, retrieve, and manage FlashMap
instances.
For each request an “input” flash map is created which stores flash attribute from any previous request and an “output” flash map is created which stores any subsequent attributes that we store in this request.
虽然这一方法看起来很完美,并且解决了表单多次提交的问题,但是它又引入了一个获取请求参数和属性的难题. 通常当我们生成一次http重定向请求的时候,被存储到请求数据会丢失,使得下一次GET请求不可能访问到这次请求中的一些有用的信息.
Flash attributes 的到来就是为了处理这一情况. Flash attributes 为一个请求存储意图为另外一个请求所使用的属性提供了一条途径. Flash attributes 在对请求的重定向生效之前被临时存储(通常是在session)中,并且在重定向之后被立即移除.
为了这样做, Flash 特性使用了两个集合. FlashMap 被用来管理 flash attributes 而 FlashMapManager 则被用来存储,获取和管理 FlashMap 实体.
对于每一次请求一个 “input” flash map 会被创建,来存储来自任何之前请求的 flash attribute 还有一个 “output” flash map 会被创建,来存储任何我们存储在这个请求中的,之后的请求参数.
In order to use Flash attribute in your Spring MVC application make sure you using version 3.1 or above. Also add mvc:annotation-driven
to spring-servlet.xml file.
<mvc:annotation-driven />
Once this is done, Flash attribute is automatically set “on” for usage. Just add attribute RedirectAttributes redirectAttributes
to your Spring controller’s method.
import org.springframework.web.servlet.mvc.support.RedirectAttributes; //... @RequestMapping(value="addcustomer", method=RequestMethod.POST) public String addCustomer(@ModelAttribute("customer") Customer customer, final RedirectAttributes redirectAttributes) { //... redirectAttributes.addFlashAttribute("message", "Successfully added.."); //... return "redirect:some_other_request_name"; }
The addFlashAttribute
method automatically add the given parameter to the output flash map and pass it to the subsequent requests.
Let us see a complete demo application which uses Flash attribute to perform POST/Redirect/GET and passes some information.
The following application displays a form to user. Once the user inputs data and submits form, the page is redirected to another page where success message is displayed. On this new redirected page, the user input is displayed.
要想在你的 Spring MVC 应用中使用 Flash attribute,要用 3.1 版本或以上。并且要在 spring-servlet.xml 文件中加入 mvc:annotation-driven。
<mvc:annotation-driven />
这些都完成之后,Flash attribute 就会自动设为“开启”,以供使用了。只需在你的 Spring controller 方法中加入RedirectAttributes redirectAttributes。
import org.springframework.web.servlet.mvc.support.RedirectAttributes; //... @RequestMapping(value="addcustomer", method=RequestMethod.POST) public String addCustomer(@ModelAttribute("customer") Customer customer, final RedirectAttributes redirectAttributes) { //... redirectAttributes.addFlashAttribute("message", "Successfully added.."); //... return "redirect:some_other_request_name"; }
addFlashAttribute 方法会自动向 output flash map 中添加给定的参数,并将它传递给后续的请求。
我们来看看一个使用 Flash attribute 来完成 POST/Redirect/GET 并传递一些信息的完整实例吧。
下面的应用向用户显示一个表单。当用户填完数据,并提交表单之后,页面会重定向到另一个显示成功信息的页面。在这个重定向的新页面中,会显示用户刚才输入的信息。
If you using Maven as dependency management, use below dependencies to add Spring 3.1 MVC support.
<dependencies> <!-- Spring 3.1 MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.1.2.RELEASE</version> </dependency> <!-- JSTL for c: tag --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>
Alternatively, you can also download following JAR files and place them under /WEB-INF/lib folder.
Add Spring support to web project by adding DispatcherServlet to web.xml.
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>Spring MVC Flash attribute example</display-name> <servlet> <servlet-name>spring</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/index.html</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> </web-app>
Also the spring-servlet uses mvc:annotation-driven
to enable mvc and also scans the project with context:component-scan
tag.
spring-servlet.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <context:component-scan base-package="net.viralpatel.controller" /> <mvc:annotation-driven /> </beans>
The Controller code uses Customer.java object as bean (command) to holds customer information.
Customer.java
package net.viralpatel.spring; public class Customer { private String firstname; private String lastname; private int age; private String email; //getter, setter methods }
The CustomerController class has 3 methods. Method showForm
is mapped with /form URL and is used to display Add New Customer form. Th method addCustomer
is mapped with URL /addcustomer and is used on POST request.
CustomerController.java
package net.viralpatel.controller; import net.viralpatel.spring.Customer; import org.springframework.stereotype.Controller; 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.servlet.mvc.support.RedirectAttributes; @Controller public class CustomerController { @RequestMapping(value="showform", method=RequestMethod.GET) public String showForm(@ModelAttribute("customer") Customer customer) { return "add_customer"; } @RequestMapping(value="addcustomer", method=RequestMethod.POST) public String addCustomer(@ModelAttribute("customer") Customer customer, final RedirectAttributes redirectAttributes) { redirectAttributes.addFlashAttribute("customer", customer); redirectAttributes.addFlashAttribute("message","Added successfully."); return "redirect:showcustomer.html"; } @RequestMapping(value="showcustomer", method=RequestMethod.GET) public String showCustomer(@ModelAttribute("customer") Customer customer) { System.out.println("cust:" + customer.getFirstname()); return "show_customer"; } }
Note how we used redirectAttributes
parameter on method addCustomer
to map flash attributes. Also we used addFlashAttribute
method to set new parameters to flash attribute.
如果你用 Maven 来做依赖管理,用下面的 dependencies 来添加 Spring 3.1 MVC 的支持。
<dependencies> <!-- Spring 3.1 MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.1.2.RELEASE</version> </dependency> <!-- JSTL for c: tag --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>
或者,你可以下载以下 JAR 文件,然后把它们放在 /WEB-INF/lib 文件夹下。
要为 web 项目添加 Spring 支持,需要在 web.xml 中添加 DispatcherServlet 。
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>Spring MVC Flash attribute example</display-name> <servlet> <servlet-name>spring</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/index.html</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> </web-app>
然后,spring-servlet 使用 mvc:annotation-driven 来支持 mvc ,并且会扫描项目中的 context:component-scan 标签。
spring-servlet.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <context:component-scan base-package="net.viralpatel.controller" /> <mvc:annotation-driven /> </beans>
Controller 的代码使用 Customer.java 对象作为 bean 来保存客户信息。
Customer.java
package net.viralpatel.spring; public class Customer { private String firstname; private String lastname; private int age; private String email; //getter, setter methods }
CustomerController 类有3个方法。showForm 方法对应 URL /form ,用来显示 Add New Customer 表单。addCustomer 方法对应 URL /addcustomer ,用来处理 POST 请求。
CustomerController.java
package net.viralpatel.controller; import net.viralpatel.spring.Customer; import org.springframework.stereotype.Controller; 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.servlet.mvc.support.RedirectAttributes; @Controller public class CustomerController { @RequestMapping(value="showform", method=RequestMethod.GET) public String showForm(@ModelAttribute("customer") Customer customer) { return "add_customer"; } @RequestMapping(value="addcustomer", method=RequestMethod.POST) public String addCustomer(@ModelAttribute("customer") Customer customer, final RedirectAttributes redirectAttributes) { redirectAttributes.addFlashAttribute("customer", customer); redirectAttributes.addFlashAttribute("message","Added successfully."); return "redirect:showcustomer.html"; } @RequestMapping(value="showcustomer", method=RequestMethod.GET) public String showCustomer(@ModelAttribute("customer") Customer customer) { System.out.println("cust:" + customer.getFirstname()); return "show_customer"; } }
注意我们在 addCustomer 方法中是如何使用 redirectAttributes 参数来添加 flash attribute 的。并且,我们是用 addFlashAttribute 方法来设置新的参数为 flash attribute。
The add customer JSP displays Add New Customer form.
add_customer.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <html> <body> <h1>Add New Customer</h1> <form:form action="addcustomer.html" method="post" commandName="customer"> <table> <tr> <td><form:label path="firstname">Firstname</form:label></td> <td><form:input path="firstname" /> </td> </tr> <tr> <td><form:label path="lastname">Lastname</form:label></td> <td><form:input path="lastname" /> </td> </tr> <tr> <td><form:label path="age">Age</form:label></td> <td><form:input path="age" /> </td> </tr> <tr> <td><form:label path="email">Email</form:label> <td><form:input path="email" /> </td> </tr> <tr> <td colspan="2"><input type="submit" value="Add Customer" /> </td> </tr> </table> </form:form> </body> </html>
Show customer JSP simply shows the customer’s first and last name and a success message set as flash attributes.
show_customer.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <html> <body> <h1>${message}</h1> ${customer.lastname}, ${customer.firstname} added successfully.. </body> </html>
Execute the web project.
URL: http://localhost:8080/SpringMVC_Flash_Attribute_Maven_example/form.html
add customer.JSP 文件显示一个 Add New Customer(添加新客户)表单。
add_customer.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <html> <body> <h1>Add New Customer</h1> <form:form action="addcustomer.html" method="post" commandName="customer"> <table> <tr> <td><form:label path="firstname">Firstname</form:label></td> <td><form:input path="firstname" /> </td> </tr> <tr> <td><form:label path="lastname">Lastname</form:label></td> <td><form:input path="lastname" /> </td> </tr> <tr> <td><form:label path="age">Age</form:label></td> <td><form:input path="age" /> </td> </tr> <tr> <td><form:label path="email">Email</form:label> <td><form:input path="email" /> </td> </tr> <tr> <td colspan="2"><input type="submit" value="Add Customer" /> </td> </tr> </table> </form:form> </body> </html>
show_customer.jsp 简单地显示客户的名和姓,以及用 flash attributes 设置的成功信息。
show_customer.jsp
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <html> <body> <h1>${message}</h1> ${customer.lastname}, ${customer.firstname} added successfully.. </body> </html>
执行这个 web 项目即可。
URL: http://localhost:8080/SpringMVC_Flash_Attribute_Maven_example/form.html