本文介紹Spring MVC中的重定向(Redirect),先回顧一下在JSP中,實現頁面跳轉的幾種方式:

  1. RequestDispatcher.forward():是在服務端起作用,當使用forward()時,Servlet引擎傳遞http請求從當前的servlet或者jsp到另外一個servlet,jsp或者普通的html文件,即你的表單(form)提交至a.jsp,在a.jsp中用到了forward()重定向到b.jsp,此時form提交的所有信息在b.jsp都可以獲得,參數自動傳遞,但forward()無法重定向至有frame的jsp文件,可以重定向到有frame的html文件,同時forward()無法帶參數傳遞,比如servlet?name=**,但可以在程序內通過response.setAttribute("name",name)來將參數傳至下一個頁面。另外,重定向後瀏覽器地址欄的URL不變,且通常在servlet中使用,不在jsp中使用。
  2. response.sendRedirect():時在用戶的瀏覽器端工作,sendRedirect()可以帶參數傳遞,比如servlet?name=**傳至下一個頁面,同時它可以重定向至不同的主機,sendRedirect()可以重定向有frame的jsp文件。重定向後在瀏覽器地址欄上會出現重定向頁面的URL。另外,由於response是jsp頁面中的隱含對象,故在jsp頁面中可以用response.sendRedirect()直接實現重定位。我們在講第三點之前,先比較一下頭兩點,比較:(1) Dispatcher.forward()是容器中的控制權的轉向,在客戶端瀏覽器地址欄中不會顯示出轉向後的地址;(2) response.sendRedirect()則是完全的跳轉,瀏覽器將會得到跳轉的地址,並重新發送請求鏈接可,這樣,從瀏覽器的地址欄中可以看到跳轉後的鏈接地址。前者更加高效,再跑題一點,在有些情況下,比如,需要跳轉到到一個其它伺服器上的資源,則必須使用HttpServletResponse.sendRequest()方法。
  3. <jsp:forward page=""/>:這個jsp標籤的底層部分是由RequestDispatcher來實現的,因此它帶有RequestDispatcher.forward()方法的所有特性。要注意,它不能改變瀏覽器地址,刷新的話會導致重複提交。

在Spring MVC中 ,跳轉其實和Controller中的return方法緊密聯繫在一起,編寫一個新的Controller,叫RedirectController:

Java代碼

  1. packageorg.springframework.samples.mvc.redirect;
  2. importorg.joda.time.LocalDate;
  3. importorg.springframework.beans.factory.annotation.Autowired;
  4. importorg.springframework.core.convert.ConversionService;
  5. importorg.springframework.stereotype.Controller;
  6. importorg.springframework.web.bind.annotation.PathVariable;
  7. importorg.springframework.web.bind.annotation.RequestMapping;
  8. importorg.springframework.web.bind.annotation.RequestMethod;
  9. importorg.springframework.web.bind.annotation.RequestParam;
  10. importorg.springframework.web.servlet.mvc.support.RedirectAttributes;
  11. importorg.springframework.web.util.UriComponents;
  12. importorg.springframework.web.util.UriComponentsBuilder;
  13. @Controller
  14. @RequestMapping("/redirect")
  15. publicclassRedirectController{
  16. privatefinalConversionServiceconversionService;
  17. @Autowired
  18. publicRedirectController(ConversionServiceconversionService){
  19. this.conversionService=conversionService;
  20. }
  21. @RequestMapping(value="/uriTemplate",method=RequestMethod.GET)
  22. publicStringuriTemplate(RedirectAttributesredirectAttrs){
  23. redirectAttrs.addAttribute("account","a123");//UsedasURItemplatevariable
  24. redirectAttrs.addAttribute("date",newLocalDate(2011,12,31));//Appendedasaqueryparameter
  25. return"redirect:/redirect/{account}";
  26. }
  27. @RequestMapping(value="/uriComponentsBuilder",method=RequestMethod.GET)
  28. publicStringuriComponentsBuilder(){
  29. Stringdate=this.conversionService.convert(newLocalDate(2011,12,31),String.class);
  30. UriComponentsredirectUri=UriComponentsBuilder.fromPath("/redirect/{account}").queryParam("date",date)
  31. .build().expand("a123").encode();
  32. return"redirect:"+redirectUri.toUriString();
  33. }
  34. @RequestMapping(value="/{account}",method=RequestMethod.GET)
  35. publicStringshow(@PathVariableStringaccount,@RequestParam(required=false)LocalDatedate){
  36. return"redirect/redirectResults";
  37. }
  38. }

先看看show這個方法:

Java代碼

  1. @RequestMapping(value="/{account}",method=RequestMethod.GET)
  2. publicStringshow(@PathVariableStringaccount,@RequestParam(required=false)LocalDatedate){
  3. return"redirect/redirectResults";
  4. }

註解好像有點多,簡單解釋一下每個註解代表什麼含義,具體詳細用法等後面講到Spring MVC的Convert部分再展開。在@RequestMapping註解中有個/{account}的url模式,在方法中也有一個參數叫accout,它帶有@PathVariable註解,即當訪問http://localhost:8080/web/redirect/1時,account變數會自動獲取到1這個值(當然不限數字,只要是字元串即可),從PathVariable也可以猜到它是解析url路徑的變數。再看@RequestParam註解,它是解析請求的參數的,如訪問http://localhost:8080/web/redirect/1?date=2012/03/26時,show方法中的date參數會自動得到2012/03/26這個值並將其轉換成LocalDate類。

最後看return,show方法返回String,並且沒有加上@ResponseBody,返回的View就是該字元串對應的View名字,我們已經有一個默認的ViewResolver,因此這個@RequestMapping最終將返回到

webapp/WEB-INF/views/redirect/redirectResults.jsp:

Html代碼

  1. <%@tagliburi="http://java.sun.com/jsp/jstl/core"prefix="c"%>
  2. <%@tagliburi="http://www.springframework.org/tags"prefix="spring"%>
  3. <%@pagesession="false"%>
  4. <html>
  5. <head>
  6. <title>RedirectResults</title>
  7. <linkhref="<c:urlvalue="/resources/form.css"/>"rel="stylesheet"type="text/css"/>
  8. </head>
  9. <body>
  10. <divclass="success">
  11. <h3>Pathvariable"account":${account}</h3>
  12. <h3>Queryparam"date":${param.date}</h3>
  13. </div>
  14. </body>
  15. </html>

此jsp取得response中的account值(類似response.setAttribute(name,value)中設置的值),和url參數傳過來的變數date(注意${param.date}中的param是jsp標準中定義的8個隱含對象(是8個吧?))並顯示在頁面。

下面馬上看到的RedirectController中另外兩個方法將會展示如何跳轉到上面的show方法的URL,先看:

Java代碼

  1. @RequestMapping(value="/uriTemplate",method=RequestMethod.GET)
  2. publicStringuriTemplate(RedirectAttributesredirectAttrs){
  3. redirectAttrs.addAttribute("account","a123");//UsedasURItemplatevariable
  4. redirectAttrs.addAttribute("date",newLocalDate(2011,12,31));//Appendedasaqueryparameter
  5. return"redirect:/redirect/{account}";
  6. }

出現了一個新類RedirectAttributes,方法中給它添加了兩個屬性,添加的屬性可以在跳轉後的頁面中獲取到。最後該方法返回"redirect:/redirect/{account}",一般理解的跳轉肯定是跳轉到某個url,SpringMVC中也不例外,

它將跳轉到http://localhost:8080/web/redirect/{account},而此URL最終將返回到一個JSP頁面上。

看看瀏覽器效果,在瀏覽器訪問 http://localhost:8080/web/redirect/uriTemplate,短暫的等待後,瀏覽器的地址欄將變成:

Html代碼

  1. http://localhost:8080/web/redirect/a123?date=12%2F31%2F11

提個問題,date=12%2F31%2F11是怎麼回事?

看下一個方法:

Java代碼

  1. @RequestMapping(value="/uriComponentsBuilder",method=RequestMethod.GET)
  2. publicStringuriComponentsBuilder(){
  3. Stringdate=this.conversionService.convert(newLocalDate(2011,12,31),String.class);
  4. UriComponentsredirectUri=UriComponentsBuilder.fromPath("/redirect/{account}").queryParam("date",date)
  5. .build().expand("a123").encode();
  6. return"redirect:"+redirectUri.toUriString();
  7. }

該方法只是前一個方法的變種。

訪問http://localhost:8080/web/redirect/uriComponentsBuilder,

短暫的等待後(應該非常短暫以至於你感覺不到),瀏覽器地址欄變成:

Html代碼

  1. http://localhost:8080/web/redirect/a123?date=12/31/11

效果一模一樣(其實還是有點不一樣,因為這個沒有出現怪異的"%2F"了),列印redirectUri出來看看:

Java代碼

  1. /redirect/a123?date=12/31/11

那麼最終return語句就是

Java代碼

  1. return"redirect:/redirect/a123?date=12/31/11";

UriComponents是一個工具類,幫助我們生成URL,比如在本例中,通過UriComponentsBuilder這個類,

有url為"/redirect/{account}",傳遞的參數為date,並且進行轉碼(encode),

結果返回的的URL就是"/redirect/a123?date=12/31/11"。(可以試著不加encode()方法看看效果)

這個結果也說明Spring MVC中的redirect可以帶參數。

[本附錄可不看,翻譯也很渣]

附錄Spring Reference Documentation中16.5.3 Redirection to views翻譯

16.5.3 重定向(redirect)到視圖(view)

前文提及,controller(控制器)返回一個view(視圖)名,view resolver(視圖解析器)解析這個特定的view。對於view technologies(視圖技術?),象JSP(JSP由servlet或者JSP引擎解析),Spring MVC中的內部解決方案是將InternalResourceViewResolver和InternalResourceView組合起來使用,這種組合採用一個內部定向(forward)或者通過Servlet自身API提供的RequestDispatcher.forward(..)方法或者RequestDispatcher.include()方法。對於其它的view technologies,如Velocity,XSLT,等等,view本身就將內容直接寫到response的輸出流裏了。

有時我們想要在view渲染前就將HTTP重定向回客戶端,這是有可能的,比如,當使用POST方式提交數據到一個Controller時,response實際上是另外一個controller的委託(比如一個成功的form表單提交)。在這種情況下,一個正常的內部定向意味著其它controller將也會看到相同的POST數據,如果將它和其它期望的數據弄混了,這就是一個潛在的問題了。在顯示結果之前進行重定向的另一個原因就是排除用戶多次提交表單數據的可能性。在這種場景下,瀏覽器會先發送一個初始POST;然後接受一個response來重定向到一個不同的URL;最後瀏覽器的地址欄會體現在重定向response中的GET方式的URL。因此,從瀏覽器的角度看,當前頁顯示的不是POST而是GET的結果。最後一個影響就是用戶不能通過「意外地」點擊了刷新,從而重複POST提交了相同的數據。刷新會到一個GET的結果頁面,而不是重複以POST方式提交數據。(說實話,這一段我沒怎麼看懂 =;=)

16.5.3.1 RedirectView

對controller來說,作為controller的response的結果來重定向的一個方式就是創建並返回一個Spring的RedirectView的實例。在這種情況下,DispatcherServlet不使用通用的view解決機制。這是由於依然重定向的view已經有了,DispatcherServlet只要簡單得讓這個view工作即可。

RedirectView通過HttpServletResponse.sendRedirect()實現,它作為一個HTTP重定向返回給客戶端瀏覽器。默認所有得model attribute會以URI模板變數得形式暴露在重定向的URL裏。保留的attribute中那些primitive types或者collections/arrays的primitive types會自動地以查詢參數的形式添加上。

如果一個model實例是為重定向特殊準備的,以查詢參數添加primitive type attributes就是我們想要的結果。然而,在加上了註解的controller中,model可能包含額外的作為渲染目的的(rendering purposes)attribute(比如:drop-down field values)。為了避免這樣的attribute出現在URL裏,一個帶有註解的controller可以聲明一個RedirectAttributes類型的參數,並使用它指定明確的attribute來讓RedirectView獲取到。如果controller方法重定向了,RedirectAttributes的內容就可以使用了,否則使用的是model的內容。

注意來自當前請求的URI模板變數,在重定向到一個URL時,是自動可獲得的,不需要額外添加,也不需要通過Model或者RedirectAttributes來添加,舉個例子:

Java代碼

  1. @RequestMapping(value="/files/{path}",method=RequestMethod.POST)
  2. publicStringupload(...){
  3. //...
  4. return"redirect:files/{path}";
  5. }

如果你使用RedirectView,這個view是被controller自身創建的,推薦你配置重定向URL,讓其注入到controller,從而it is not baked into the controller,而是在上下文中帶著view名配置的。下一節我們繼續討論。

16.5.3.2 redirect:前綴

因為controller本身可以創建RedirectView,所以我們使用RedirectView可以工作得很好,不可避免的一個事實是controller得知道這個Redirect,這讓事情緊緊得耦合在一起了,controller不應該知道response是如何處理的,通常它應該只關心注入進來的view名。

這個特殊的redirect:前綴可以達到這個目的,如果一個view名以前綴名redirect:方式返回,UrlBasedViewrResolver(以及它的子類)會識別出來這是一個特殊的indication,這個indication 是一個redirect需要的。剩下的view名將會作為重定向的URL來處理。

如果controller返回的是RedirectView,實際的效果是一樣的,但現在controller自身可以操作邏輯意義上的view名,一個邏輯意義上的view名,如redirect:/myapp/some/resources會重定向關聯到當前Servlet上下文,同時這樣的redirect:http://myhost.com/some/arbitrary/path的view名會重定向到一個絕對路徑的URL上。

16.5.3.3 forward:前綴

也可以給view名使用一個特殊的forward:前綴,它會被UrlBasedViewResolver及其子類解析。它給view名創建一個InternalResourceView(實際使用RequestDispatcher.forward()),剩下的view名就是一個URL,然而,這個前綴對於InternalResourceViewResolver和InternalResourceView(比如JSP)是不可用的。但是這個前綴在你使用其它view technology時還是可以提供一些幫助的。但是仍然要強制forward到一個能被Servlet/JSP引擎處理的資源。(注意你也可以將多個view解析器串起來,替代)。(說實話,這段沒怎麼明白 譯者)

16.5.4 ContentNegotiatingViewResolver (跟我們講的關聯不大,略去 譯者)


推薦閱讀:
查看原文 >>
相關文章