1. Overview
1.概述
Web applications are often dependent on user input to meet several of their use cases. As a result, form submission is a heavily used mechanism to gather and process data for such apps.
网络应用程序经常依赖用户输入来满足他们的一些使用情况。因此,表单提交是一个大量使用的机制,用于收集和处理此类应用程序的数据。
In this tutorial, we’ll learn how Spring’s flash attributes can help us with the form submission workflow securely and reliably.
在本教程中,我们将学习Spring的flash属性如何安全、可靠地帮助我们进行表单提交工作流程。
2. Flash Attributes Basics
2.闪存属性基础知识
Before we can comfortably use flash attributes, we need to build up a decent level of understanding of the form submission workflow and a few key related concepts.
在我们能够自如地使用flash属性之前,我们需要对表单提交的工作流程和一些关键的相关概念建立起相当程度的理解。
2.1. Post/Redirect/Get Pattern
2.1.发布/重定向/获取模式
A naive way to engineer a web form would be to use a single HTTP POST request that takes care of submission and gives back a confirmation through its response. However, such design exposes the risk of duplicate processing of POST requests, in case the user ends up refreshing the page.
设计网络表单的一种天真方式是使用单一的HTTP POST请求来处理提交并通过其响应返回确认。然而,这样的设计暴露了重复处理POST请求的风险,因为用户最终会刷新页面。
To mitigate the issue of duplicate processing, we can create the workflow as a sequence of interconnected requests in a specific order — namely, POST, REDIRECT, and GET. In short, we call this the Post/Redirect/Get (PRG) pattern for form submission.
为了减轻重复处理的问题,我们可以将工作流创建为按特定顺序的相互关联的请求序列–即POST、REDIRECT和GET。简而言之,我们称之为表单提交的POST/REDIRECT/GET(PRG)模式。
On receiving the POST request, the server processes it and then transfers control to make a GET request. Subsequently, the confirmation page displays based on the response of the GET request. Ideally, even if the last GET request gets attempted more than once, there shouldn’t be any adverse side-effects.
在收到POST请求后,服务器会对其进行处理,然后转移控制权以发出GET请求。随后,根据GET请求的响应显示确认页面。理想情况下,即使最后一个GET请求被尝试了不止一次,也不应该有任何不利的副作用。
2.2. Life Cycle of Flash Attributes
2.2.闪存属性的生命周期
To complete the form submission using the PRG pattern, we’ll need to transfer information from the initial POST request to the final GET request after redirection.
为了使用PRG模式完成表单提交,我们需要将信息从最初的POST请求转移到重定向后的最终GET请求。
Unfortunately, we can neither use the RequestAttributes nor the SessionAttributes. That’s because the former won’t survive a redirection across different controllers, while the latter will last for the entire session even after the form submission is over.
不幸的是,我们既不能使用 RequestAttributes 也不能使用 SessionAttributes。这是因为前者无法在不同控制器之间的重定向中存活,而后者将在整个会话中持续存在,甚至在表单提交结束之后。
But, we don’t need to worry as Spring’s web framework provides flash attributes that can fix this exact problem.
但是,我们不需要担心,因为Spring的网络框架提供了Flash属性,可以解决这个确切的问题。
Let’s see the methods in the RedirectAttributes interface that can help us to use flash attributes in our project:
让我们看看RedirectAttributes接口中的方法,可以帮助我们在项目中使用flash属性。
RedirectAttributes addFlashAttribute(String attributeName, @Nullable Object attributeValue);
RedirectAttributes addFlashAttribute(Object attributeValue);
Map<String, ?> getFlashAttributes();
Flash attributes are short-lived. As such, these are stored temporarily in some underlying storage, just before the redirect. They remain available for the subsequent request after redirect, and then they’re gone.
Flash属性是短命的。因此,在重定向之前,这些属性被暂时存储在一些底层存储中。它们在重定向后的后续请求中仍然可用,然后它们就消失了。
2.3. FlashMap Data Structure
2.3.FlashMap数据结构
Spring provides an abstract data structure called FlashMap for storing the flash attributes as key-value pairs.
Spring提供了一个名为FlashMap的抽象数据结构,用于将flash属性存储为键值对。
Let’s take a look at the definition of the FlashMap class:
让我们看一下FlashMap类的定义。
public final class FlashMap extends HashMap<String, Object> implements Comparable<FlashMap> {
@Nullable
private String targetRequestPath;
private final MultiValueMap<String, String> targetRequestParams
= new LinkedMultiValueMap<>(4);
private long expirationTime = -1;
}
We can notice that the FlashMap class inherits its behavior from the HashMap class. As such, a FlashMap instance can store a key-value mapping of the attributes. Also, we can tie a FlashMap instance to be used only by a specific redirect URL.
我们可以注意到,FlashMap类从HashMap类继承其行为。因此,FlashMap实例可以存储属性的键-值映射。此外,我们还可以将一个FlashMap实例绑定在一起,使其只能被特定的重定向URL使用。
Furthermore, every request has two FlashMap instances, namely Input FlashMap and Output FlashMap, which play an important role in the PRG pattern:
此外,每个请求都有两个FlashMap实例,即输入FlashMap和输出FlashMap,它们在PRG模式中发挥着重要作用。
- Output FlashMap is used in the POST request to temporarily save the flash attributes and send them to the next GET request after the redirect
- Input FlashMap is used in the final GET request to access the read-only flash attributes that were sent by the previous POST request before the redirect
2.4. FlashMapManager and RequestContextUtils
2.4. FlashMapManager和RequestContextUtils
As the name suggests, we can use FlashMapManager to manage the FlashMap instances.
顾名思义,我们可以使用FlashMapManager来管理FlashMap实例。
First, let’s take a look at the definition of this strategy interface:
首先,我们来看看这个策略界面的定义。
public interface FlashMapManager {
@Nullable
FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}
Simply put, we can say that FlashMapManager allows us to read, update, and save FlashMap instances in some underlying storage.
简单地说,我们可以说FlashMapManager允许我们在一些底层存储中读取、更新和保存FlashMap实例。
Next, let’s familiarize ourselves with a few static methods available in the RequestContextUtils abstract utility class.
接下来,让我们熟悉一下RequestContextUtils抽象实用程序类中的几个静态方法。
To keep our focus within the scope of this tutorial, we’ll limit our coverage to the methods that are relevant to flash attributes:
为了使我们的重点保持在本教程的范围内,我们将把我们的覆盖面限制在与flash属性有关的方法上。
public static Map<String, ?> getInputFlashMap(HttpServletRequest request);
public static FlashMap getOutputFlashMap(HttpServletRequest request);
public static FlashMapManager getFlashMapManager(HttpServletRequest request);
public static void saveOutputFlashMap(String location,
HttpServletRequest request, HttpServletResponse response);
We can use these methods to retrieve the input/output FlashMap instances, get the FlashMapManager for a request, and save a FlashMap instance.
我们可以使用这些方法来检索输入/输出FlashMap实例,为一个请求获取FlashMapManager,并保存一个FlashMap实例。
3. Form Submission Use Case
3.表格提交用例
By now, we’ve established a basic understanding of different concepts around flash attributes. So, let’s move further and use them in a poetry contest web application.
到现在为止,我们已经对围绕Flash属性的不同概念有了基本的了解。因此,让我们更进一步,在一个诗歌比赛的网络应用中使用它们。
Our poetry contest app has a simple use case of accepting poem entries from different poets through the submission of a form. Furthermore, a contest entry will have the necessary information related to a poem, such as a title, a body, and the author’s name.
我们的诗歌比赛应用程序有一个简单的用例,即通过提交表格接受不同诗人的诗歌作品。此外,参赛作品将有与诗歌相关的必要信息,如标题、正文和作者姓名。
3.1. Thymeleaf Configuration
3.1.Thymeleaf配置
We’ll be using Thymeleaf, which is a Java template engine for creating dynamic web pages through simple HTML templates.
我们将使用Thymeleaf,它是一个Java模板引擎,用于通过简单的HTML模板创建动态网页。
First, we need to add the spring-boot-starter-thymeleaf dependency to our project’s pom.xml:
首先,我们需要将spring-boot-starter-thymeleaf依赖性添加到我们项目的pom.xml。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
Next, we can define some of the Thymeleaf-specific properties in our application.properties file located in the src/main/resources directory:
接下来,我们可以在src/main/resources目录下的pplication.properties文件中定义一些Thymeleaf的特定属性。
spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
Having defined these properties, we can now create all our views under the /src/main/resources/templates directory. In turn, Spring will append the .html suffix to all the views named inside our controller.
定义了这些属性后,我们现在可以在/src/main/resources/templates目录下创建所有的视图。反过来,Spring会在我们控制器内命名的所有视图上加上.html后缀。
3.2. Domain Model
3.2.领域模型
Up next, let’s define our domain model in a Poem class:
接下来,让我们在一个Poem类中定义我们的领域模型。
public class Poem {
private String title;
private String author;
private String body;
}
Further, we can add the isValidPoem() static method in our Poem class to help us validate that the fields don’t allow empty strings:
此外,我们可以在我们的Poem类中添加isValidPoem()静态方法,帮助我们验证字段不允许有空字符串。
public static boolean isValidPoem(Poem poem) {
return poem != null && Strings.isNotBlank(poem.getAuthor())
&& Strings.isNotBlank(poem.getBody())
&& Strings.isNotBlank(poem.getTitle());
}
3.3. Create Form
3.3.创建表格
Now, we’re ready to create our submission form. For that, we need an endpoint /poem/submit that will serve a GET request to show the form to the user:
现在,我们准备创建我们的提交表单。为此,我们需要一个端点/poem/submit,它将提供一个GET请求来向用户展示表单。
@GetMapping("/poem/submit")
public String submitGet(Model model) {
model.addAttribute("poem", new Poem());
return "submit";
}
Here, we’ve used a model as a container to hold the poem-specific data provided by the user. Moreover, the submitGet method returns a view served by the submit view.
在这里,我们使用了一个模型作为容器来保存用户提供的特定诗歌数据。此外,submitGet方法返回一个由submit视图提供的视图。
Additionally, we want to bind the POST form with the model attribute poem:
此外,我们希望将POST表单与模型属性poem绑定。
<form action="#" method="post" th:action="@{/poem/submit}" th:object="${poem}">
<!-- form fields for poem title, body, and author -->
</form>
3.4. Post/Redirect/Get Submission Flow
3.4.发布/重定向/获取提交流程
Now, let’s enable the POST action for the form. To do that, we’ll create the /poem/submit endpoint in the PoemSubmission controller to serve the POST request:
现在,让我们启用表单的POST动作。为此,我们将在PoemSubmission控制器中创建/poem/submit端点,以提供POST请求。
@PostMapping("/poem/submit")
public RedirectView submitPost(
HttpServletRequest request,
@ModelAttribute Poem poem,
RedirectAttributes redirectAttributes) {
if (Poem.isValidPoem(poem)) {
redirectAttributes.addFlashAttribute("poem", poem);
return new RedirectView("/poem/success", true);
} else {
return new RedirectView("/poem/submit", true);
}
}
We can notice that if the submission is successful, then control transfers to the /poem/success endpoint. Also, we added the poem data as a flash attribute before initiating the redirect.
我们可以注意到,如果提交成功,那么控制权就转移到/poem/success 端点。另外,在启动重定向之前,我们把诗的数据作为一个flash属性加入。
Now, we need to show a confirmation page to the user, so let’s implement the functionality for the /poem/success endpoint that’ll serve the GET request:
现在,我们需要向用户显示一个确认页面,所以让我们实现/poem/success端点的功能,为GET请求服务。
@GetMapping("/poem/success")
public String getSuccess(HttpServletRequest request) {
Map<String, ?> inputFlashMap = RequestContextUtils.getInputFlashMap(request);
if (inputFlashMap != null) {
Poem poem = (Poem) inputFlashMap.get("poem");
return "success";
} else {
return "redirect:/poem/submit";
}
}
It’s important to note here that we need to validate the FlashMap before we decide to redirect to the success page.
这里需要注意的是,我们在决定重定向到成功页面之前,需要验证FlashMap。
Finally, let’s use the flash attribute poem inside our success page to show the title of the poem submitted by the user:
最后,让我们在成功页面内使用flash属性poem来显示用户提交的诗歌的标题。
<h1 th:if="${poem}">
<p th:text="${'You have successfully submitted poem titled - '+ poem?.title}"/>
Click <a th:href="@{/poem/submit}"> here</a> to submit more.
</h1>
4. Conclusion
4.总结
In this tutorial, we learned a few concepts around the Post/Redirect/Get pattern and flash attributes. And, we also saw flash attributes in action with a simple form-submission in a Spring Boot web application.
在本教程中,我们学习了围绕Post/Redirect/Get模式和Flash属性的一些概念。而且,我们还看到了Flash属性在Spring Boot网络应用程序中的简单表单提交中的作用。
As always, the complete source code for the tutorial is available over on GitHub.
一如既往,该教程的完整源代码可在GitHub上获取。