Spring MVC 中的 forward 和 redirect

        Spring MVC 中,咱們在返回邏輯視圖時,框架會經過 viewResolver 來解析獲得具體的 View,而後向瀏覽器渲染。假設邏輯視圖名爲 hello,經過配置,咱們 配置某個 ViewResolver 以下:
java

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<description>
		假如邏輯試圖名爲 "hello",所以 viewResolver 將解析成 /WEB-INF/jsp/hello.jsp
	</description>
	<property name="order" value="10" />
	<property name="prefix" value="/WEB-INF/jsp/" />
	<property name="suffix" value=".jsp" />
</bean>


        實際上,框架仍是經過 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 前綴來達到轉發到其它資源的目的:
web

public String handle() {
    // return "forward:/hello" => 轉發到可以匹配 /hello 的 controller 上
    // return "hello" => 實際上仍是轉發,只不過是框架會找到該邏輯視圖名對應的 View 並渲染
    // return "/hello" => 同 return "hello"
    return "forward:/hello";
}



        同理,若是咱們想重定向到某個資源,咱們能夠經過 redirect 前綴來達到重定向到其它資源的目的:
spring

public String handle() {
    // 重定向到 /hello 資源
    return "redirect:/hello";
}


        還記得 java web 中的轉發和重定向 這篇文章嗎?我強調過,若是想作轉發操做,不須要寫 contextPath;若是想作重定向操做,推薦寫包括 contextPath 在內的 url。所以,在使用 Spring MVC 的 redirect 前綴時,裏面是有坑的!

        仍然假設應用程序的 contextPath 爲 /ctx。咱們來看看 RedirectView.renderMergedOutputModel 的片斷:
瀏覽器

protected void renderMergedOutputModel(
    Map<String, Object> model, HttpServletRequest request, HttpServletResponse response)
    throws IOException {

  // Prepare target URL.
  StringBuilder targetUrl = new StringBuilder();
  if (this.contextRelative && getUrl().startsWith("/")) {
    // Do not apply context path to relative URLs.
    targetUrl.append(request.getContextPath());
  }
  targetUrl.append(getUrl());

  // ...

  sendRedirect(request, response, targetUrl.toString(), this.http10Compatible);
}

protected void sendRedirect(
    HttpServletRequest request, HttpServletResponse response, String targetUrl, boolean http10Compatible)
    throws IOException {

  if (http10Compatible) {
    // Always send status code 302.
    response.sendRedirect(response.encodeRedirectURL(targetUrl));
  }
  else {
    HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
    response.setStatus(statusCode.value());
    response.setHeader("Location", response.encodeRedirectURL(targetUrl));
  }
}


        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:
app

protected View createView(String viewName, Locale locale) throws Exception {
  // If this resolver is not supposed to handle the given view,
  // return null to pass on to the next resolver in the chain.
  if (!canHandle(viewName, locale)) {
    return null;
  }
  // Check for special "redirect:" prefix.
  if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
    String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
    return new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
  }
  // Check for special "forward:" prefix.
  if (viewName.startsWith(FORWARD_URL_PREFIX)) {
    String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
    return new InternalResourceView(forwardUrl);
  }
  // Else fall back to superclass implementation: calling loadView.
  return super.createView(viewName, locale);
}


        UrlBasedViewResolver 的 redirectContextRelative 的默認值爲 true,這意味着,只要重定向的資源以 / 開頭,那麼 spring 會幫你添加 contextPath。站在 Spring MVC 的角度上來講,/ 開頭的資源就是相對於當前應用程序,這和 forward 同樣了。所以,若是你肯定重定向操做是在同一應用程序中操做,那就使用 Spring MVC 的默認值吧,這樣就不須要你寫 contextPath 了。注意,這樣作有隱患!當重定向的資源是其它應用程序時,除非你瞭解機制,不然請不要這麼作!框架

相關文章
相關標籤/搜索