spring mvc 中,我們在傳回邏輯視圖時,架構會通過 viewresolver 來解析得到具體的
view,然後向浏覽器渲染。假設邏輯視圖名為 hello,通過配置,我們配置某個 viewresolver
如下:
xml代碼:
1
2
3
4
5
6
7
8
<code><bean</code><code>class</code><code>=</code><code>"org.springframework.web.servlet.view.internalresourceviewresolver"</code><code>></code>
<code> </code><code><description></code>
<code> </code><code>假如邏輯試圖名為</code><code>"hello"</code><code>,是以 viewresolver 将解析成 /web-inf/jsp/hello.jsp</code>
<code> </code><code></description></code>
<code> </code><code><property name=</code><code>"order"</code>
<code>value=</code><code>"10"</code>
<code>/></code>
<code> </code><code><property name=</code><code>"prefix"</code>
<code>value=</code><code>"/web-inf/jsp/"</code>
<code> </code><code><property name=</code><code>"suffix"</code>
<code>value=</code><code>".jsp"</code>
<code></bean></code>
實際上,架構還是通過 forward 的方式轉發到了 /web-inf/jsp/hello.jsp。如果邏輯視圖名是
/hello,實際還是轉發到了 /web-inf/jsp/hello.jsp,即 /web-inf/jsp//hello.jsp 等同于
/web-inf/jsp/hello.jsp。
現在有個問題,如果 /hello 就是某個
controller 的映射,我想轉發到這個 controller,怎麼辦?我們可以通過 forward
字首來達到轉發到其它資源的目的:
java代碼:
<code>public</code> <code>string handle() {</code>
<code> </code><code>// return "forward:/hello" => 轉發到能夠比對 /hello 的 controller 上</code>
<code> </code><code>// return "hello" => 實際上還是轉發,隻不過是架構會找到該邏輯視圖名對應的 view 并渲染</code>
<code> </code><code>// return "/hello" => 同 return "hello"</code>
<code> </code><code>return</code>
<code>"forward:/hello"</code><code>;</code>
<code>}</code>
同理,如果我們想重定向到某個資源,我們可以通過 redirect
字首來達到重定向到其它資源的目的:
<code> </code><code>// 重定向到 /hello 資源</code>
<code>"redirect:/hello"</code><code>;</code>
如果想做轉發操作,不需要寫 contextpath;如果想做重定向操作,推薦寫包括 contextpath 在内的
url。是以,在使用 spring mvc 的 redirect
字首時,裡面是有坑的!
仍然假設應用程式的 contextpath 為
/ctx。我們來看看 redirectview.rendermergedoutputmodel
的片段:
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<code>protected</code>
<code>void</code> <code>rendermergedoutputmodel(</code>
<code> </code><code>map<string, object> model, httpservletrequest request, httpservletresponse response)</code>
<code> </code><code>throws</code>
<code>ioexception {</code>
<code> </code><code>// prepare target url.</code>
<code> </code><code>stringbuilder targeturl =</code><code>new</code>
<code>stringbuilder();</code>
<code> </code><code>if</code>
<code>(</code><code>this</code><code>.contextrelative && geturl().startswith(</code><code>"/"</code><code>)) {</code>
<code> </code><code>// do not apply context path to relative urls.</code>
<code> </code><code>targeturl.append(request.getcontextpath());</code>
<code> </code><code>}</code>
<code> </code><code>targeturl.append(geturl());</code>
<code> </code><code>// ...</code>
<code> </code><code>sendredirect(request, response, targeturl.tostring(),</code><code>this</code><code>.http10compatible);</code>
<code>void</code> <code>sendredirect(</code>
<code> </code><code>httpservletrequest request, httpservletresponse response, string targeturl,</code><code>boolean</code>
<code>http10compatible)</code>
<code>(http10compatible) {</code>
<code> </code><code>// always send status code 302.</code>
<code> </code><code>response.sendredirect(response.encoderedirecturl(targeturl));</code>
<code> </code><code>else</code>
<code>{</code>
<code> </code><code>httpstatus statuscode = gethttp11statuscode(request, response, targeturl);</code>
<code> </code><code>response.setstatus(statuscode.value());</code>
<code> </code><code>response.setheader(</code><code>"location"</code><code>, response.encoderedirecturl(targeturl));</code>
sendredirect 方法沒什麼特别的,它就是調用 httpservletresponse 的 sendredirect
方法而已。是以,關鍵點就是 rendermergedoutputmodel 方法對轉發的資源的 url 進行處理了。最終的 url 與
contextrelative 和你要重定向的資源是否以 / 開頭有關!當且僅當 rendermergedoutputmodel 為
true,并且你要重定向的資源是以 / 開頭,spring 會在該資源前添加
contextpath。
response.sendredirect() 的參數,如果不以 /
開頭,那麼容器最終計算出來的資源是相對于做重定向操作的資源的 url;如果以 / 開頭,容器将它視為相對于主機的
url。如此說來,spring 的 redirectview 怎麼着都隻能将資源重定向到目前應用程式上。将 url 開頭的 /
去掉不是解決之道,是以本機的其它應用程式的 contextpath 必定是以 / 開頭,是以我們要想辦法設定 contextrelative
了。
redirectview 自身持有 contextrelative 屬性,用于在程式中通過 new
操作符來構造一個 redirectview 并可以設定 contextrelative。當處理請求的方法傳回類型為 string 時,是通過
viewresolver 來解析得到 view 的。urlbasedviewresolver 就是能夠解析出 redirectview 的
viewresolver。該 viewresolver 持有 redirectcontextrelative 屬性,當它發現邏輯視圖名以 "redirect:"
開頭時,會将自身持有的 redirectcontextrelative 傳入 redirectview 的構造函數以建立
redirectview。是以我們通過注冊 urlbasedviewresolver 時設定 redirectcontextrelative 以達到控制
redirectview 修改 url 的行為。urlbasedviewresolver 解析出
view:
<code>view createview(string viewname, locale locale)</code><code>throws</code>
<code>exception {</code>
<code> </code><code>// if this resolver is not supposed to handle the given view,</code>
<code> </code><code>// return null to pass on to the next resolver in the chain.</code>
<code>(!canhandle(viewname, locale)) {</code>
<code>null</code><code>;</code>
<code> </code><code>// check for special "redirect:" prefix.</code>
<code>(viewname.startswith(redirect_url_prefix)) {</code>
<code> </code><code>string redirecturl = viewname.substring(redirect_url_prefix.length());</code>
<code>new</code> <code>redirectview(redirecturl, isredirectcontextrelative(), isredirecthttp10compatible());</code>
<code> </code><code>// check for special "forward:" prefix.</code>
<code>(viewname.startswith(forward_url_prefix)) {</code>
<code> </code><code>string forwardurl = viewname.substring(forward_url_prefix.length());</code>
<code>new</code> <code>internalresourceview(forwardurl);</code>
<code> </code><code>// else fall back to superclass implementation: calling loadview.</code>
<code> </code><code>return</code>
<code>super</code><code>.createview(viewname, locale);</code>
urlbasedviewresolver 的 redirectcontextrelative 的預設值為 true,這意味着,隻要重定向的資源以 /
開頭,那麼 spring 會幫你添加 contextpath。站在 spring mvc 的角度上來說,/ 開頭的資源就是相對于目前應用程式,這和
forward 一樣了。是以,如果你确定重定向操作是在同一應用程式中操作,那就使用 spring mvc 的預設值吧,這樣就不需要你寫 contextpath
了。注意,這樣做有隐患!當重定向的資源是其它應用程式時,除非你了解機制,否則請不要這麼做!