1. 概述
Web 應用程序通常依賴用户輸入以滿足其各種用例。因此,表單提交是一種常用的機制,用於收集和處理此類應用程序的數據。
在本教程中,我們將學習如何使用 Spring 的 Flash Attributes 安全可靠地處理表單提交工作流程。
2. Flash 屬性基礎
在我們可以舒適地使用 Flash 屬性之前,我們需要對錶單提交工作流程以及一些相關的關鍵概念建立起一定的理解。
2.1. POST/重定向/GET 模式
使用單個 HTTP POST 請求來處理表單提交,並通過其響應返回確認信息,是一種簡單的表單工程方式。然而,這種設計暴露了重複處理 POST 請求的風險,當用户刷新頁面時,可能會發生這種情況。
為了緩解重複處理的問題,我們可以將工作流程設計為一系列相互連接的請求,按照特定順序執行——即 POST、重定向和 GET。 簡而言之,我們稱之為 Post/Redirect/Get (PRG) 模式,用於表單提交。
在收到 POST 請求後,服務器會對其進行處理,然後將控制權轉移到發出 GET 請求。隨後,確認頁面會根據 GET 請求的響應顯示。理想情況下,即使最後一個 GET 請求被多次嘗試,也不應該產生任何不良副作用。
2.2. Flash屬性生命週期
為了使用 PRG (Post/Redirect/Get) 模式完成表單提交,我們需要將初始 POST 請求的信息傳遞到最終的 GET 請求,在重定向之後。
不幸的是,我們既不能使用 <em><a href="https://en.wikipedia.org/wiki/Post/Redirect/Get">PRG</a></em > 模式,也不能使用 <em><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/request/RequestAttributes.html">RequestAttributes</a></em > 或 `SessionAttributes。 這是因為前者在跨控制器重定向時無法保持生存狀態,而後者則會在表單提交結束後仍然存在整個會話。
但是,不用擔心,Spring 的 Web 框架提供了 Flash 屬性,可以解決這個問題。
以下是 <em><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/support/RedirectAttributes.html">RedirectAttributes</a></em > 接口中可以幫助我們使用 Flash 屬性的方法:
RedirectAttributes addFlashAttribute(String attributeName, @Nullable Object attributeValue);
RedirectAttributes addFlashAttribute(Object attributeValue);
Map<String, ?> getFlashAttributes();Flash屬性具有短暫性. 因此,它們會在重定向之前臨時存儲在底層存儲中。重定向後,這些屬性仍然可用,隨後便消失。
2.3. FlashMap 數據結構
Spring 提供了一個抽象的數據結構,名為 FlashMap,用於以鍵值對的形式存儲閃存屬性。
以下是 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;
}我們可以注意到,FlashMap 類繼承了 HashMap 類的行為。因此,一個 FlashMap 實例可以存儲屬性的鍵值對映射。 此外,可以將 FlashMap 實例綁定為僅供特定重定向 URL 使用。
此外,每個請求都有兩個 FlashMap 實例,即 Input FlashMap 和 Output FlashMap,它們在 PRG 模式中起着重要作用:
- Output FlashMap 用於 POST 請求中臨時保存閃存屬性並將其發送到重定向後下一個 GET 請求
- Input FlashMap 用於最終的 GET 請求中訪問先前 POST 請求重定向前發送的只讀閃存屬性
2.4. <em >FlashMapManager</em > 和 <em >RequestContextUtils</em >
正如其名稱所示,我們可以使用 <em >FlashMapManager</em > 來管理 <em >FlashMap</em > 實例。
首先,讓我們來查看一下這個策略接口的定義:
public interface FlashMapManager {
@Nullable
FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}簡單來説,我們可以説 FlashMapManager 允許我們讀取、更新和保存 FlashMap 實例到底層存儲中。
接下來,讓我們熟悉一下在 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);我們可以使用這些方法來檢索輸入/輸出 FlashMap 實例,獲取請求的 FlashMapManager,並保存一個 FlashMap 實例。
3. 表單提交用例
在此之前,我們已經對閃存屬性的概念有了基本的理解。現在,讓我們進一步應用這些概念,並在一個詩歌比賽 Web 應用程序中使用它們。
我們的詩歌比賽應用程序有一個簡單的用例,即通過提交表單接受來自不同詩人的詩歌作品。此外,一個比賽作品將包含與詩歌相關的必要信息,例如標題、內容和作者姓名。
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>3.1.5</version>
</dependency>接下來,我們可以定義一些 Thymeleaf 相關的屬性,並將其添加到我們的 application.properties 文件中,該文件位於 src/main/resources 目錄中:
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 類中 定義我們的領域模型:
public class Poem {
private String title;
private String author;
private String body;
}我們還可以向我們的 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. 創建表單
現在,我們準備創建提交表單。為此,我們需要一個端點 /poem/submit,它將向用户發送一個 GET 請求,以顯示錶單:
@GetMapping("/poem/submit")
public String submitGet(Model model) {
model.addAttribute("poem", new Poem());
return "submit";
}在這裏,我們使用模型作為容器來存儲用户提供的詩歌特定數據。 此外,submitGet 方法返回由submit視圖提供的視圖。
此外,我們希望將 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 提交流程
現在,讓我們為表單啓用 POST 操作。為此,我們將創建 <em /poem/submit 端點,在 PoemSubmission 控制器中來處理 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);
}
}我們注意到,如果提交成功,則控制會轉移到 /poem/success 端點。此外,我們還添加了詩歌數據作為 flash 屬性,在發起重定向之前。
現在,我們需要向用户顯示確認頁面,因此讓我們實現對 /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";
}
}需要注意的是,在決定重定向到成功頁面之前,我們必須先驗證 FlashMap 。
最後,我們將使用 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. 結論
在本教程中,我們學習了關於 Post/Redirect/Get 模式和 Flash 屬性的一些概念。我們還通過在 Spring Boot Web 應用程序中進行簡單的表單提交,觀察了 Flash 屬性的實際應用。