在處理完POST請求后, 通常來講一個最佳實踐就是執行一下重定向。除了其他的一些因素外,這樣做能夠防止用戶點擊瀏覽器的刷新按鈕或后退箭頭時,客戶端重新執行危險的POST請求。
在控制器方法返回的視圖名稱中,我們借助了“ redirect:” 前綴的力量。當控制器方法返回的String 值 以“ redirect:” 開頭 的 話, 那么 這個 String 不是 用來 查找 視圖 的, 而是 用來 指導 瀏覽器 進行 重定向 的 路徑。 我們 可以 回頭 看一下 程序 清單 5. 17, 可以看到 processRegistration() 方法 返回 的“ redirect: String” 如下 所示:
return "redirect:/ spitter/" + spitter. getUsername();
“redirect:” 前綴 能夠 讓 重定向 功能 變得 非常 簡單。 你 可能 會想 Spring 很難 再讓 重定向 功能 變得 更簡單 了。 但是, 請 稍等: Spring 為重 定向 功能 還 提供 了 一些 其他 的 輔助 功能。 具體 來講, 正在 發起 重定向 功能 的 方法 該 如何 發送 數據 給 重定向 的 目標 方法 呢? 一般 來講, 當 一個 處理器 方法 完成 之后, 該 方法 所指 定的 模型 數據 將會 復制 到 請求 中, 並作 為 請求 中的 屬性, 請求 會 轉發( forward) 到 視圖 上進 行 渲染。 因為 控制器 方法 和 視圖 所 處理 的 是 同一個 請求, 所以 在 轉發 的 過程中, 請求 屬性 能夠 得以 保存。
但是, 當 控制器 的 結果是 重定向 的 話, 原始 的 請求 就 結束 了, 並且 會 發起 一個 新的 GET 請求。 原始 請求 中 所帶 有的 模型 數據 也就 隨着請求一起 消亡 了。 在 新的 請求 屬性 中, 沒有 任何 的 模型 數據, 這個 請求 必須 要 自己 計算 數據。
圖 1 模型 的 屬性 是以 請求 屬性 的 形式 存放 在 請求 中的, 在 重定向后無法存活
顯然, 對於 重定向 來說, 模型 並不能 用來 傳遞 數據。 但是 我們 也有 一些 其他 方案, 能夠 從 發起 重定向 的 方法 傳遞 數據 給 處理 重定向 方法 中: 使用 URL 模板 以 路徑 變量 和/ 或 查詢 參數 的 形式 傳遞 數據; 通過 flash 屬性 發送 數據。
首先, 我們 看一下 Spring 如何 幫助 我們 通過 路徑 變量 和/ 或 查詢 參數 的 形式 傳遞 數據。
通過URL模板進行重定向
通過 路徑 變量 和 查詢 參數 傳遞 數據 看起來 非常 簡單。 例如, 在 程序 清單 5. 19 中, 我們 以 路徑 變量 的 形式 傳遞 了 新 創建 Spitter 的 username。 但是 按照 現在 的 寫法, username 的 值 是 直接 連接 到 重定向 String 上 的。 這 能夠 正常 運行, 但是 還 遠遠 不能 說 沒有 問題。 當 構建 URL 或 SQL 查詢 語句 的 時候, 使用 String 連接 是 很 危險 的。
return "redirect:/ spitter/{ username}";
除了 連接 String 的 方式 來 構建 重定向 URL, Spring 還 提供 了 使用 模板 的 方式 來 定義 重定向 URL。 例如, 在 程序 清單 5. 19 中, processRegistration() 方法 的 最后 一行 可以 改寫 為 如下 的 形式:
@RequestMapping( value="/ register", method= POST)
public String processRegistration( Spitter spitter, Model model) {
spitterRepository. save( spitter);
model. addAttribute(" username", spitter. getUsername());
return "redirect:/ spitter/{ username}";
}
現在, username 作為 占位符 填充 到了 URL 模板 中, 而 不是 直接 連接 到 重定向 String 中, 所以 username 中 所有 的 不安全 字符 都會 進行 轉義。 這樣 會 更加 安全, 這里 允許 用戶 輸入 任何 想要 的 內容 作為 username, 並 會 將其 附加 到 路徑 上。
除此之外, 模型 中 所有 其他 的 原始 類型 值 都可以 添加 到 URL 中 作為 查詢 參數。 作為 樣 例, 假設 除了 username 以外, 模型 中 還要 包含 新 創建 Spitter 對象 的 id 屬性, 那 processRegistration() 方法 可以 改寫 為 如下 的 形式:
@RequestMapping( value="/ register", method= POST)
public String processRegistration( Spitter spitter, Model model) {
spitterRepository. save( spitter);
model. addAttribute(" username", spitter. getUsername());
model. addAttribute(" spitterId", spitter. getId());
return "redirect:/ spitter/{ username}";
}
所 返回 的 重定向 String 並沒有 太大 的 變化。 但是, 因為 模型 中的 spitterId 屬性 沒有 匹配 重定向 URL 中的 任何 占位符, 所以 它 會 自動 以 查詢 參數 的 形式 附加 到 重定向 URL 上。
如果 username 屬 性的 值 是 habuma 並且 spitterId 屬 性的 值 是 42, 那么 結果 得到 的 重定向 URL 路徑 將會 是“/ spitter/ habuma? spitterId= 42”。
通過 路徑 變量 和 查詢 參數 的 形式 跨 重定向 傳遞 數據 是 很 簡單 直接 的 方式, 但它 也有 一定 的 限制。 它 只能 用來 發送 簡單 的 值, 如 String 和 數字 的 值。 在 URL 中, 並沒有 辦法 發送 更為 復雜 的 值, 但這 正是 flash 屬性 能夠 提供 幫助 的 領域。
使用 flash 屬性
假設 我們 不 想在 重定向 中 發送 username 或 ID 了, 而是 要 發送 實際 的 Spitter 對象。 如果 我們 只 發送 ID 的 話, 那么 處理 重定向 的 方法 還需 要從 數據庫 中 查找 才能 得到 Spitter 對象。 但是, 在 重定向 之前, 我們 其實 已經 得到 了 Spitter 對象。 為什么 不 將其 發送 給 處理 重定向 的 方法, 並將 其 展現 出來 呢?
Spitter 對象 要比 String 和 int 更為 復雜。 因此, 我們 不能 像 路徑 變量 或 查詢 參數 那么 容易 地 發送 Spitter 對象。 它 只能 設置 為 模型 中的 屬性。
但是, 正如 我們 前面 所 討論 的 那樣, 模型 數據 最終 是以 請求 參數 的 形式 復制 到 請求 中的, 當 重定向 發生 的 時候, 這些 數據 就會 丟失。 因此, 我們 需要 將 Spitter 對象 放到 一個 位置, 使其 能夠 在 重定向 的 過程中 存活 下來。
有個 方案 是將 Spitter 放到 會話 中。 會話 能夠 長期存在, 並且 能夠 跨 多個 請求。 所以 我們 可以 在 重定向 發生 之前 將 Spitter 放到 會話 中, 並在 重定 向后, 從 會話 中將 其 取出。 當然, 我們 還要 負責 在 重定向 后 在 會話 中將 其 清理 掉。
實際上, Spring 也 認為 將 跨 重定向 存活 的 數據 放到 會話 中 是一 個 很不 錯的 方式。 但是, Spring 認為 我們 並不 需要 管理 這些 數據, 相反, Spring 提供 了 將 數據 發送 為 flash 屬性( flash attribute) 的 功能。 按照 定義, flash 屬性 會 一直 攜帶 這些 數據 直到 下一 次 請求, 然后 才會 消失。
Spring 提供 了 通過 RedirectAttributes 設置 flash 屬性 的 方法, 這是 Spring 3. 1 引入 的 Model 的 一個 子 接口。 RedirectAttributes 提供 了 Model 的 所有 功能, 除此之外, 還有 幾個 方法 是 用來 設置 flash 屬性 的。
具體 來講, RedirectAttributes 提供 了 一組 addFlashAttribute() 方法 來 添加 flash 屬性。 重新 看一下 processRegistration() 方法, 我們 可以 使用 addFlashAttribute() 將 Spitter 對象 添加 到 模型 中:
@RequestMapping( value="/ register", method= POST)
public String processRegistration( Spitter spitter, RedirectAttributes model) {
spitterRepository. save( spitter);
model. addAttribute(" username", spitter. getUsername());
model. addFlashAttribute(" spitter", spitter);
return "redirect:/ spitter/{ username}";
}
在這里, 我們 調用 了 addFlashAttribute() 方法, 並將 spitter 作為 key, Spitter 對象 作為 值。 另外, 我們 還可以 不 設置 key 參數, 讓 key 根據 值 的 類型類型 自行 推斷 得出:
model. addFlashAttribute( spitter);
因為 我們 傳遞 了 一個 Spitter 對象 給 addFlashAttribute() 方法, 所以 推斷 得到 的 key 將會 是 spitter。
在 重定向 執行 之前, 所有 的 flash 屬性 都會 復制 到會 話中。 在 重定 向后, 存在 會話 中的 flash 屬性 會被 取出, 並從 會話 轉移 到 模型 之中。 處理 重定向 的 方法 就能 從 模型 中 訪問 Spitter 對象 了, 就 像 獲取 其他 的 模型 對象 一樣。 圖 2闡述 了 它是 如何 運行 的。
圖 2 flash 屬性 保存 在 會話 中, 然后 再放 到 模型 中, 因此 能夠 在 重定向 的 過程中 存活
為了 完成 flash 屬性 的 流程, 如下 展現 了 更新 版本 的 showSpitterProfile() 方法, 在 從 數據庫 中 查找 之前, 它 會 首先 從 模型 中 檢查 Spitter 對象:
@RequestMapping( value="/{ username}", method= GET)
public String showSpitterProfile( @PathVariable String username, Model model) {
if (!model. containsAttribute(" spitter")) {
model. addAttribute( spitterRepository. findByUsername( username));
}
return "profile";
}
可以 看到, showSpitterProfile() 方法 所做 的 第一 件事 就是 檢查 是否 存有 key 為 spitter 的 model 屬性。 如果 模型 中 包含 spitter 屬性, 那就 什么 都不 用 做了。 這里 面 包含 的 Spitter 對象 將會 傳遞 到 視圖 中進 行 渲染。 但是 如果 模型 中 不 包含 spitter 屬 性的 話, 那么 showSpitterProfile() 將會 從 Repository 中 查找 Spitter, 並將 其 存放 到 模型 中。
摘自:[美] Craig Walls 沃爾斯. Spring實戰(第4版)