1. 概述
本教程將重點介紹在 Spring 中實現重定向的方法,並討論每種策略背後的原因。
2. 為什麼要進行重定向?
首先,我們來考慮一下在 Spring 應用中進行重定向的原因。
當然,可能存在許多例子和原因。例如,我們可能需要處理 POST 表單數據、規避雙重提交問題,或者將執行流程委派給另一個控制器方法。
這裏做一個簡短的説明:典型的 Post/Redirect/Get 模式並不能充分解決雙重提交問題,刷新頁面之前初始提交未完成的情況仍然可能導致雙重提交。
3. 使用 RedirectView 進行重定向
讓我們從這種簡單方法開始,直接跳轉到示例:
@Controller
@RequestMapping("/")
public class RedirectController {
@GetMapping("/redirectWithRedirectView")
public RedirectView redirectWithUsingRedirectView(
RedirectAttributes attributes) {
attributes.addFlashAttribute("flashAttribute", "redirectWithRedirectView");
attributes.addAttribute("attribute", "redirectWithRedirectView");
return new RedirectView("redirectedUrl");
}
}在幕後,RedirectView 將會觸發 HttpServletResponse.sendRedirect(),從而實際執行重定向。
請注意,我們在這裏是如何將重定向屬性注入到方法中的。框架將承擔繁重的工作,並允許我們與這些屬性進行交互。
我們添加了模型屬性 attribute,它將被暴露為 HTTP 查詢參數。模型必須只包含對象——通常是字符串或可以轉換為字符串的對象。
現在,讓我們使用簡單的 curl 命令測試我們的重定向:
curl -i http://localhost:8080/spring-rest/redirectWithRedirectView以下是我們的結果:
HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Location:
http://localhost:8080/spring-rest/redirectedUrl?attribute=redirectWithRedirectView使用前綴 <em redirect:> 進行重定向
之前的做法——使用 <em RedirectView> ——對於幾個原因來説是不理想的。
首先,我們現在與 Spring API 耦合,因為我們在代碼中直接使用了 <em RedirectView>。
其次,我們現在需要從一開始就知道,在實現該控制器操作時,結果總是會是一個重定向,而這並不總是真的。
更好的選擇是使用前綴 <em redirect:>。重定向視圖名稱像任何其他邏輯視圖名稱一樣注入到控制器中。 控制器根本不瞭解重定向正在發生。
下面是如何實現的:
@Controller
@RequestMapping("/")
public class RedirectController {
@GetMapping("/redirectWithRedirectPrefix")
public ModelAndView redirectWithUsingRedirectPrefix(ModelMap model) {
model.addAttribute("attribute", "redirectWithRedirectPrefix");
return new ModelAndView("redirect:/redirectedUrl", model);
}
}
當視圖名稱以 redirect: 前綴返回時,UrlBasedViewResolver (及其所有子類) 將將其識別為一種特殊指示,表示需要執行重定向。重定向 URL 將使用視圖名稱的其餘部分。
請注意,當我們在這裏使用邏輯視圖名稱 redirect:/redirectedUrl 時,我們正在執行一個相對於當前 Servlet 上下文的重定向。
如果需要重定向到絕對 URL,可以使用諸如 redirect: http://localhost:8080/spring-redirect-and-forward/redirectedUrl 這樣的名稱。
因此,當我們執行 curl 命令時:
curl -i http://localhost:8080/spring-rest/redirectWithRedirectPrefix我們將會立即被重定向:
HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Location:
http://localhost:8080/spring-rest/redirectedUrl?attribute=redirectWithRedirectPrefix5. 使用前綴轉發 轉發時,使用:
現在我們來看如何做一些稍微不同的事情:轉發。
在代碼之前,我們先回顧一下 轉發與重定向語義的快速、高層次概述:
- 重定向 會使用 302 狀態碼,並將新的 URL 放入 Location 頭部;瀏覽器/客户端會向新的 URL 發出另一個請求。
- 轉發 完全在服務器端進行。Servlet 容器會將相同的請求轉發到目標 URL;瀏覽器中的 URL 不會發生變化。
現在讓我們來看一下代碼:
@Controller
@RequestMapping("/")
public class RedirectController {
@GetMapping("/forwardWithForwardPrefix")
public ModelAndView redirectWithUsingForwardPrefix(ModelMap model) {
model.addAttribute("attribute", "forwardWithForwardPrefix");
return new ModelAndView("forward:/redirectedUrl", model);
}
}
與 redirect: 相同,forward: 前綴將由 UrlBasedViewResolver 和其子類解析。 內部,這將創建一個 InternalResourceView,它將通過 RequestDispatcher.forward() 將請求轉發到新的視圖。
當我們使用 curl 執行命令時:
curl -I http://localhost:8080/spring-rest/forwardWithForwardPrefix
我們將會收到 HTTP 405 (方法不允許):
HTTP/1.1 405 Method Not Allowed
Server: Apache-Coyote/1.1
Allow: GET
Content-Type: text/html;charset=utf-8總結一下,與重定向解決方案中遇到的兩個請求相比,在本例中我們只有一個請求從瀏覽器/客户端發出到服務器端。此前重定向添加的屬性,當然也已消失。
6. 使用 RedirectAttributes 屬性
接下來,讓我們更詳細地瞭解在重定向中傳遞屬性,充分利用框架中的 RedirectAttributes:
@GetMapping("/redirectWithRedirectAttributes")
public RedirectView redirectWithRedirectAttributes(RedirectAttributes attributes) {
attributes.addFlashAttribute("flashAttribute", "redirectWithRedirectAttributes");
attributes.addAttribute("attribute", "redirectWithRedirectAttributes");
return new RedirectView("redirectedUrl");
}
正如我們之前所見,我們可以直接在方法中注入 attributes 對象,這使得這個機制非常易於使用。
請注意,我們還添加了 flash 屬性。 這是一個不會進入 URL 的屬性。
通過這種屬性,我們可以稍後僅在最終重定向的目標方法中,使用 @ModelAttribute(“flashAttribute”) 訪問 flash 屬性。
@GetMapping("/redirectedUrl")
public ModelAndView redirection(
ModelMap model,
@ModelAttribute("flashAttribute") Object flashAttribute) {
model.addAttribute("redirectionAttribute", flashAttribute);
return new ModelAndView("redirection", model);
}
因此,總結一下,如果我們使用 curl 測試功能:
curl -i http://localhost:8080/spring-rest/redirectWithRedirectAttributes我們將被重定向到新的位置:
HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=4B70D8FADA2FD6C22E73312C2B57E381; Path=/spring-rest/; HttpOnly
Location: http://localhost:8080/spring-rest/redirectedUrl;
jsessionid=4B70D8FADA2FD6C22E73312C2B57E381?attribute=redirectWithRedirectAttributes這樣一來,使用 RedirectAttributes 代替 ModelMap 能夠讓我們僅在重定向操作中共享 某些屬性,這兩種方法之間。
7. 不使用前綴的替代配置
現在,讓我們探索一種替代配置:在不使用前綴的情況下進行重定向。
要實現這一點,我們需要使用一個 org.springframework.web.servlet.view.XmlViewResolver。
<bean class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="location">
<value>/WEB-INF/spring-views.xml</value>
</property>
<property name="order" value="0" />
</bean>
這取代了我們在先前配置中使用的 org.springframework.web.servlet.view.InternalResourceViewResolver。
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
</bean>
我們還需要在配置中定義一個 RedirectView Bean:
<bean id="RedirectedUrl" class="org.springframework.web.servlet.view.RedirectView">
<property name="url" value="redirectedUrl" />
</bean>
現在我們可以通過引用這個新的 Bean 來按 ID 觸發重定向:
@Controller
@RequestMapping("/")
public class RedirectController {
@GetMapping("/redirectWithXMLConfig")
public ModelAndView redirectWithUsingXMLConfig(ModelMap model) {
model.addAttribute("attribute", "redirectWithXMLConfig");
return new ModelAndView("RedirectedUrl", model);
}
}
為了測試它,我們將再次使用 curl 命令:
curl -i http://localhost:8080/spring-rest/redirectWithRedirectView以下是我們得到的結果:
HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Location:
http://localhost:8080/spring-rest/redirectedUrl?attribute=redirectWithRedirectView8. 重定向 HTTP POST 請求
對於像銀行支付等用例,我們可能需要重定向一個 HTTP POST 請求。根據返回的 HTTP 狀態碼,POST 請求可以重定向到 HTTP GET 或 POST。
根據 HTTP 1.1 協議 參考,狀態碼 301 (永久移動) 和 302 (找到) 允許請求方法從 POST 更改為 GET。該規範還定義了狀態碼 307 (臨時重定向) 和 308 (永久重定向),這些狀態碼不允許請求方法從 POST 更改為 GET。
下面是重定向 POST 請求到另一個 POST 請求的代碼示例:
@PostMapping("/redirectPostToPost")
public ModelAndView redirectPostToPost(HttpServletRequest request) {
request.setAttribute(
View.RESPONSE_STATUS_ATTRIBUTE, HttpStatus.TEMPORARY_REDIRECT);
return new ModelAndView("redirect:/redirectedPostToPost");
}@PostMapping("/redirectedPostToPost")
public ModelAndView redirectedPostToPost() {
return new ModelAndView("redirection");
}現在我們將使用 curl 命令測試 POST 重定向:
curl -L --verbose -X POST http://localhost:8080/spring-rest/redirectPostToPost我們被重定向到目標位置:
> POST /redirectedPostToPost HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.49.0
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 08 Aug 2017 07:33:00 GMT
{"id":1,"content":"redirect completed"}9. 使用參數轉發
現在,讓我們考慮一個場景,即我們希望將參數傳遞給另一個帶有 forward 前綴的 RequestMapping。
在這種情況下,我們可以使用 HttpServletRequest 在調用之間傳遞參數。
以下是一個需要將 param1 和 param2 發送到另一個映射 forwardedWithParams 的方法:
@RequestMapping(value="/forwardWithParams", method = RequestMethod.GET)
public ModelAndView forwardWithParams(HttpServletRequest request) {
request.setAttribute("param1", "one");
request.setAttribute("param2", "two");
return new ModelAndView("forward:/forwardedWithParams");
}事實上,forwardedWithParams映射可以在完全獨立的控制器中存在,也不需要位於同一個控制器中。
@RequestMapping(value="/forwardWithParams", method = RequestMethod.GET)
@Controller
@RequestMapping("/")
public class RedirectParamController {
@RequestMapping(value = "/forwardedWithParams", method = RequestMethod.GET)
public RedirectView forwardedWithParams(
final RedirectAttributes redirectAttributes, HttpServletRequest request) {
redirectAttributes.addAttribute("param1", request.getAttribute("param1"));
redirectAttributes.addAttribute("param2", request.getAttribute("param2"));
redirectAttributes.addAttribute("attribute", "forwardedWithParams");
return new RedirectView("redirectedUrl");
}
}為了説明,我們嘗試以下 curl 命令:
curl -i http://localhost:8080/spring-rest/forwardWithParams以下是結果:
HTTP/1.1 302 Found
Date: Fri, 19 Feb 2021 05:37:14 GMT
Content-Language: en-IN
Location: http://localhost:8080/spring-rest/redirectedUrl?param1=one¶m2=two&attribute=forwardedWithParams
Content-Length: 0
如我們所見,param1 和 param2 從第一個控制器傳遞到第二個控制器。最終,它們出現在名為 redirectedUrl 的重定向中,而 forwardedWithParams 指向該重定向。
10. 結論
本文闡述了在 Spring 中實現重定向的三種不同方法,介紹瞭如何處理和傳遞屬性,以及如何處理 HTTP POST 請求的重定向。