天天看點

Spring MVC 中的 forward 和 redirect

 spring mvc 中,我們在傳回邏輯視圖時,架構會通過 viewresolver 來解析得到具體的

view,然後向浏覽器渲染。假設邏輯視圖名為 hello,通過配置,我們配置某個 viewresolver

如下:

xml代碼:

1

2

3

4

5

6

7

8

<code>&lt;bean</code><code>class</code><code>=</code><code>"org.springframework.web.servlet.view.internalresourceviewresolver"</code><code>&gt;</code>

<code>    </code><code>&lt;description&gt;</code>

<code>        </code><code>假如邏輯試圖名為</code><code>"hello"</code><code>,是以 viewresolver 将解析成 /web-inf/jsp/hello.jsp</code>

<code>    </code><code>&lt;/description&gt;</code>

<code>    </code><code>&lt;property name=</code><code>"order"</code>

<code>value=</code><code>"10"</code>

<code>/&gt;</code>

<code>    </code><code>&lt;property name=</code><code>"prefix"</code>

<code>value=</code><code>"/web-inf/jsp/"</code>

<code>    </code><code>&lt;property name=</code><code>"suffix"</code>

<code>value=</code><code>".jsp"</code>

<code>&lt;/bean&gt;</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" =&gt; 轉發到能夠比對 /hello 的 controller 上</code>

<code>    </code><code>// return "hello" =&gt; 實際上還是轉發,隻不過是架構會找到該邏輯視圖名對應的 view 并渲染</code>

<code>    </code><code>// return "/hello" =&gt; 同 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&lt;string, object&gt; 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 &amp;&amp; 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

了。注意,這樣做有隐患!當重定向的資源是其它應用程式時,除非你了解機制,否則請不要這麼做!