Spring Web应用程序中的Flash属性指南

2023/05/19

1. 概述

Web应用程序通常依赖于用户输入来满足它们的几个用例。 因此,表单提交是收集和处理此类应用程序数据的常用机制。

在本教程中,我们介绍Spring的flash属性如何帮助我们安全可靠地完成表单提交工作流。

2. Flash属性基础

首先,我们需要对表单提交工作流和一些关键的相关概念有一定了解。

2.1 Post/Redirect/Get模式

设计Web表单的一种简单方法是使用单个HTTP POST请求来处理提交并通过其响应返回确认。 但是,这样的设计暴露了重复处理POST请求的风险,不能防止用户最终刷新页面。

为了减轻重复处理的问题,我们可以将工作流创建为按特定顺序互连的请求序列,即POST、REDIRECT和GET。 简而言之,我们称之为表单提交的Post/Redirect/Get(PRG)模式。

在接收到POST请求后,服务器对其进行处理,然后转移控制权并发出GET请求。 随后,根据GET请求的响应显示确认页面。理想情况下,即使最后一次GET请求被多次尝试,也不会有任何不利的副作用。

2.2 Flash属性的生命周期

要使用PRG模式完成表单提交,我们需要将信息从初始POST请求传输到重定向后的最终GET请求。

不幸的是,我们既不能使用RequestAttributes也不能使用SessionAttributes。 这是因为前者无法在不同控制器之间进行重定向,而后者即使在表单提交结束后也会持续整个会话。

但是,我们不需要担心,因为Spring的web框架提供了可以解决这个确切问题的flash属性。

让我们看看RedirectAttributes接口中的方法,这些方法可以帮助我们在项目中使用flash属性:

public interface RedirectAttributes extends Model {

    RedirectAttributes addFlashAttribute(String attributeName, @Nullable Object attributeValue);

    RedirectAttributes addFlashAttribute(Object attributeValue);

    Map<String, ?> getFlashAttributes();
}

Flash属性是短暂的,因此,在重定向之前,这些数据被临时存储在一些底层存储中。 它们在重定向后仍然可用于后续请求,然后它们就消失了。

2.3 FlashMap数据结构

Spring提供了一个名为FlashMap的抽象数据结构,用于将flash属性存储为键值对。

下面是FlashMap类的定义:

public final class FlashMap extends HashMap<String, Object> implements Comparable<FlashMap> {

    @Nullable
    private String targetRequestPath;

    private final MultiValueMap<String, String> targetRequestParams = new LinkedMultiValueMap<>(3);

    private long expirationTime = -1;
}

我们可以注意到FlashMap类继承了HashMap类的行为,因此,FlashMap实例可以存储属性的键值对。 此外,我们可以将FlashMap实例绑定为仅由特定重定向URL使用。

此外,每个请求都有两个FlashMap实例,即Input FlashMap和Output FlashMap,它们在PRG模式中起着重要作用:

  • POST请求中使用Output FlashMap临时保存flash属性,并在重定向后发送到下一个GET请求。
  • Input FlashMap用于在最终的GET请求中访问重定向之前由上一个POST请求发送的只读flash属性。

2.4 FlashMapManager和RequestContextUtils

顾名思义,我们可以使用FlashMapManager来管理FlashMap实例。

首先让我们看看这个策略接口的定义:

public interface FlashMapManager {

    @Nullable
    FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);

    void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}

简单地说,FlashMapManager允许我们在一些底层存储中读取、更新和保存FlashMap实例。

接下来,让我们熟悉一下RequestContextUtils抽象工具类中可用的一些静态方法。

为了将我们的重点放在本教程的范围内,这里只列出与flash属性相关的方法:

public abstract class RequestContextUtils {

    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);
}

我们可以使用这些方法来检索input/output FlashMap实例,获取请求的FlashMapManager,并保存FlashMap实例。

3. 表单提交用例

到目前为止,我们对Flash属性的不同概念有了基本的了解,现在通过一个例子演示它们的使用。

我们的诗歌比赛应用程序有一个简单的用例,通过提交表单接收来自不同诗人的诗歌条目。 此外,参赛作品将包含与诗歌相关的必要信息,例如标题、正文和作者姓名。

3.1 Thymeleaf配置

我们将使用Thymeleaf,这是一个Java模板引擎,用于通过简单的HTML模板创建动态网页。

首先,我们需要将spring-boot-starter-thymeleaf依赖添加到我们项目的pom.xml中:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.6.1</version>
</dependency>

接下来,我们可以在位于src/main/resources目录中的application.properties文件中定义一些Thymeleaf特定的属性:

spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

定义了这些属性后,我们可以在/src/main/resources/templates目录下创建所有视图。 然后,Spring会将.html后缀附加到我们控制器内命名的所有视图。

3.2 域模型

接下来,我们定义一个Poem模型类:

@Getter
@Setter
public class Poem {
    private String title;
    private String author;
    private String body;
}

此外,我们可以在Poem类中添加isValidPoem()静态方法,用于校验字段不允许为空字符串:

public class Poem {

    public static boolean isValidPoem(Poem poem) {
        return poem != null && Strings.isNotBlank(poem.getAuthor()) && Strings.isNotBlank(poem.getBody()) && Strings.isNotBlank(poem.getTitle());
    }
}

3.3 创建表单

现在,我们准备开始创建提交表单。为此,我们需要一个端点“/poem/submit”来提供GET请求以向用户显示表单

@Controller
public class PoemSubmission {

    @GetMapping("/poem/submit")
    public String submitGet(Model model) {
        model.addAttribute("poem", new Poem());
        return "submit";
    }
}

在这里,我们使用Model作为容器来保存用户提供的特定于Poem的数据。 此外,submitGet方法返回一个由“submit”视图提供的视图。

并且我们希望将POST表单与模型属性poem绑定:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Poetry Contest: Submission</title>
</head>
<body>
<form action="#" method="post" th:action="@{/poem/submit}" th:object="${poem}">
    <!-- form fields for poem title, body, and author -->
</form>
</body>
</html>

3.4 Post/Redirect/Get提交流

现在,让我们为表单启用POST操作。为此,我们将在PoemSubmission控制器中创建“/poem/submit”端点来响应POST请求

@Controller
public class PoemSubmission {

    @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);
        }
    }
}

我们可以注意到,如果提交成功,则控制权转移到“/poem/success”端点。 此外,在启动重定向之前,我们将poem数据添加为flash属性。

现在,我们需要向用户显示一个确认页面,因此让我们实现“/point/success”端点的功能,它将为GET请求提供服务:

@Controller
public class PoemSubmission {

    @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";
        }
    }
}

这里需要注意的是,在决定重定向到成功页面之前,我们需要验证FlashMap

最后,我们使用成功页面中的flash属性poem来显示用户提交的诗歌的标题:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Poetry Contest: Thank You</title>
</head>
<body>
<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>
<h1 th:unless="${poem}">
    Click <a th:href="@{/poem/submit}"> here</a> to submit a poem.
</h1>
</body>
</html>

4. 总结

在本教程中,我们介绍了一些关于Post/Redirect/Get模式和flash属性的概念。 并且,我们通过一个简单的表单提交演示了flash属性的用法。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章