表單重複提交是 Web 開發經典痛點。Spring MVC 提供 RedirectAttributes 配合 PRG 模式,以零 Session 開銷優雅解決數據傳遞與重複提交問題。
用戶提交 POST → Controller 處理 → RedirectAttributes 存入 FlashMap (請求域,重定向後自動清除) → 302 重定向至 GET 頁面 → GET 處理器從 Model 讀取 Flash 屬性 → 渲染視圖
關鍵優勢: Flash 屬性僅在重定向鏈路中生存一次,刷新頁面不會重複提交,無需手動清理 Session。
@PostMapping("/login")
public String login(@Valid UserForm form, BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
// 1. 驗證失敗:攜帶錯誤信息與表單數據重定向回登錄頁
if (bindingResult.hasErrors()) {
redirectAttributes.addFlashAttribute("error.password.error", "密碼格式不正確");
redirectAttributes.addFlashAttribute("user", form); // 回顯表單數據
return "redirect:/login"; // PRG 核心:重定向而非 forward
}
// 2. 業務處理...
try {
userService.authenticate(form.getUsername(), form.getPassword());
} catch (AuthenticationException e) {
redirectAttributes.addFlashAttribute("error.auth", "用戶名或密碼錯誤");
return "redirect:/login";
}
// 3. 成功重定向至首頁
return "redirect:/dashboard";
}
@GetMapping("/login")
public String showLoginForm(Model model) {
// 無需額外代碼,Spring 自動將 FlashMap 合併到 Model
// 模板中直接使用 ${error.password.error} ${user.username}
return "login";
}
<form method="post" th:action="@{/login}" th:object="${user}">
<div class="form-group">
<label>用戶名</label>
<input type="text" th:field="*{username}" class="form-control"
th:classappend="${#fields.hasErrors('username') ? 'is-invalid' : ''}" />
<div class="invalid-feedback" th:errors="*{username}"></div>
</div>
<div th:if="${error.password.error}" class="alert alert-danger"
th:text="${error.password.error}"></div>
<div th:if="${error.auth}" class="alert alert-danger" th:text="${error.auth}"></div>
<button type="submit" class="btn btn-primary">登錄</button>
</form>
btn.disabled = trueRedis SETNX idempotent:order:{orderId} EX 300)@PostMapping("/orders")
public String createOrder(OrderDTO dto, RedirectAttributes ra) {
String idempotentKey = "idempotent:order:" + dto.getClientToken();
if (!redisTemplate.opsForValue().setIfAbsent(idempotentKey, "1", 5, TimeUnit.MINUTES)) {
ra.addFlashAttribute("error", "請勿重複提交訂單");
return "redirect:/orders/new";
}
try {
orderService.create(dto);
} finally {
redisTemplate.delete(idempotentKey); // 或由異步任務清理
}
return "redirect:/orders/success";
}
| 誤區 | 後果 | 正確做法 |
|---|---|---|
用 addAttribute 而非 addFlashAttribute | 參數拼接在 URL,暴露敏感信息,刷新仍存在 | 敏感/臨時數據必用 Flash 屬性 |
驗證失敗用 forward: 而非 redirect: | 瀏覽器地址欄仍為 POST URL,刷新重複提交 | 永遠 redirect: 實現 PRG |
| Flash 屬性存放大對象 | 默認存 Session,佔用內存 | 僅存錯誤碼、ID 等輕量數據 |
RedirectAttributes+ PRG 是 Spring MVC 表單處理的標準範式:零 Session 污染、自動生命周期管理、天然防重複提交。配合冪等性 Key 構建雙重防線,徹底解決業務層面重複提交。