如前所述,控制器通常都會返回一個邏輯視圖名,然后視圖解析器會把它解析到一個具體的視圖技術上去渲染。對于一些可以由Servlet或JSP引擎來處理的視圖技術,比如JSP等,這個解析過程通常是由InternalResourceViewResolver
和InternalResourceView
協(xié)作來完成的,而這通常會調用Servlet的APIRequestDispatcher.forward(..)
方法或RequestDispatcher.include(..)
方法,并發(fā)生一次內部的轉發(fā)(forward)或引用(include)。而對于其他的視圖技術,比如Velocity、XSLT等,視圖本身的內容是直接被寫回響應流中的。
有時,我們想要在視圖渲染之前,先把一個HTTP重定向請求發(fā)送回客戶端。比如,當一個控制器成功地接受到了POST
過來的數(shù)據(jù),而響應僅僅是委托另一個控制器來處理(比如一次成功的表單提交)時,我們希望發(fā)生一次重定向。在這種場景下,如果只是簡單地使用內部轉發(fā),那么意味著下一個控制器也能看到這次POST
請求攜帶的數(shù)據(jù),這可能導致一些潛在的問題,比如可能會與其他期望的數(shù)據(jù)混淆,等。此外,另一種在渲染視圖前對請求進行重定向的需求是,防止用戶多次提交表單的數(shù)據(jù)。此時若使用重定向,則瀏覽器會先發(fā)送第一個POST
請求;請求被處理后瀏覽器會收到一個重定向響應,然后瀏覽器直接被重定向到一個不同的URL,最后瀏覽器會使用重定向響應中攜帶的URL發(fā)起一次GET
請求。因此,從瀏覽器的角度看,當前所見的頁面并不是POST
請求的結果,而是一次GET
請求的結果。這就防止了用戶因刷新等原因意外地提交了多次同樣的數(shù)據(jù)。此時刷新會重新GET
一次結果頁,而不是把同樣的POST
數(shù)據(jù)再發(fā)送一遍。
強制重定向的一種方法是,在控制器中創(chuàng)建并返回一個Spring重定向視圖RedirectView
的實例。它會使得DispatcherServlet
放棄使用一般的視圖解析機制,因為你已經(jīng)返回一個(重定向)視圖給DispatcherServlet
了,所以它會構造一個視圖來滿足渲染的需求。緊接著RedirectView
會調用HttpServletResponse.sendRedirect()
方法,發(fā)送一個HTTP重定向響應給客戶端瀏覽器。
如果你決定返回RedirectView
,并且這個視圖實例是由控制器內部創(chuàng)建出來的,那我們更推薦在外部配置重定向URL然后注入到控制器中來,而不是寫在控制器里面。這樣它就可以與視圖名一起在配置文件中配置。關于如何實現(xiàn)這個解耦,請參考 重定向前綴——redirect:一小節(jié)。
模型中的所有屬性默認都會考慮作為URI模板變量被添加到重定向URL中。剩下的其他屬性,如果是基本類型或者基本類型的集合或數(shù)組,那它們將被自動添加到URL的查詢參數(shù)中去。如果model是專門為該重定向所準備的,那么把所有基本類型的屬性添加到查詢參數(shù)中可能是我們期望那個的結果。但是,在包含注解的控制器中,model可能包含了專門作為渲染用途的屬性(比如一個下拉列表的字段值等)。為了避免把這樣的屬性也暴露在URL中,@RequestMapping
方法可以聲明一個RedirectAttributes
類型的方法參數(shù),用它來指定專門供重定向視圖RedirectView
取用的屬性。如果重定向成功發(fā)生,那么RedirectAttributes
對象中的內容就會被使用;否則則使用模型model中的數(shù)據(jù)。
RequestMappingHandlerAdapter
提供了一個"ignoreDefaultModelOnRedirect"
標志。它被用來標記默認Model
中的屬性永遠不應該被用于控制器方法的重定向中??刂破鞣椒☉撀暶饕粋€RedirectAttributes
類的參數(shù)。如果不聲明,那就沒有參數(shù)被傳遞到重定向的視圖RedirectView
中。在MVC命名空間或MVC Java編程配置方式中,為了維持向后的兼容性,這個標志都仍被保持為false
。但如果你的應用是一個新的項目,那么我們推薦把它的值設置成true
。
請注意,當前請求URI中的模板變量會在填充重定向URL的時候自動對應用可見,而不需要顯式地在Model
或RedirectAttributes
中再添加屬性。請看下面的例子:
@RequestMapping(path = "/files/{path}", method = RequestMethod.POST)
public String upload(...) {
// ...
return "redirect:files/{path}";
}
另外一種向重定向目標傳遞數(shù)據(jù)的方法是通過 閃存屬性(Flash Attributes)。與其他重定向屬性不同,flash屬性是存儲在HTTP session中的(因此不會出現(xiàn)在URL中)。更多內容,請參考 21.6 使用閃存屬性一節(jié)。
盡管使用RedirectView
來做重定向能工作得很好,但如果控制器自身還是需要創(chuàng)建一個RedirectView
,那無疑控制器還是了解重定向這么一件事情的發(fā)生。這還是有點不盡完美,不同范疇的耦合還是太強??刂破髌鋵嵅粦撊リP心響應會如何被渲染。In general it should operate only in terms of view names that have been injected into it.
一個特別的視圖名前綴能完成這個解耦:redirect:
。如果返回的視圖名中含有redirect:
前綴,那么UrlBasedViewResolver
(及它的所有子類)就會接受到這個信號,意識到這里需要發(fā)生重定向。然后視圖名剩下的部分會被解析成重定向URL。
這種方式與通過控制器返回一個重定向視圖RedirectView
所達到的效果是一樣的,不過這樣一來控制器就可以只專注于處理并返回邏輯視圖名了。如果邏輯視圖名是這樣的形式:redirect:/myapp/some/resource
,他們重定向路徑將以Servlet上下文作為相對路徑進行查找,而邏輯視圖名如果是這樣的形式:redirect:http://myhost.com/some/arbitrary/path
,那么重定向URL使用的就是絕對路徑。
注意的是,如果控制器方法注解了@ResponseStatus
,那么注解設置的狀態(tài)碼值會覆蓋RedirectView
設置的響應狀態(tài)碼值。
對于最終會被UrlBasedViewResolver
或其子類解析的視圖名,你可以使用一個特殊的前綴:forward:
。這會導致一個InternalResourceView
視圖對象的創(chuàng)建(它最終會調用RequestDispatcher.forward()
方法),后者會認為視圖名剩下的部分是一個URL。因此,這個前綴在使用InternalResourceViewResolver
和InternalResourceView
時并沒有特別的作用(比如對于JSP來說)。但當你主要使用的是其他的視圖技術,而又想要強制把一個資源轉發(fā)給Servlet/JSP引擎進行處理時,這個前綴可能就很有用(或者,你也可能同時串聯(lián)多個視圖解析器)。
與redirect:
前綴一樣,如果控制器中的視圖名使用了forward:
前綴,控制器本身并不會發(fā)覺任何異常,它關注的仍然只是如何處理響應的問題。
更多建議: