Spring MVC 中,我们在返回逻辑视图时,框架会通过 viewResolver 来解析得到具体的 View,然后向浏览器渲染。假设逻辑视图名为 hello,通过配置,我们配置某个 ViewResolver 如下:
Xml代码:
1
2
3
4
5
6
7
8
|
<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
前缀来达到转发到其它资源的目的:
Java代码:
1
2
3
4
5
6
|
public String handle() {
// return "forward:/hello" => 转发到能够匹配 /hello 的 controller 上
// return "hello" => 实际上还是转发,只不过是框架会找到该逻辑视图名对应的 View 并渲染
// return "/hello" => 同 return "hello"
return
"forward:/hello" ;
} |
同理,如果我们想重定向到某个资源,我们可以通过 redirect 前缀来达到重定向到其它资源的目的:
java代码:
1
2
3
4
|
public String handle() {
// 重定向到 /hello 资源
return
"redirect:/hello" ;
} |
如果想做转发操作,不需要写 contextPath;如果想做重定向操作,推荐写包括 contextPath 在内的
url。因此,在使用 Spring MVC 的 redirect
前缀时,里面是有坑的!
仍然假设应用程序的 contextPath 为
/ctx。我们来看看 RedirectView.renderMergedOutputModel
的片段:
java代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
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:
java代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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 了。注意,这样做有隐患!当重定向的资源是其它应用程序时,除非你了解机制,否则请不要这么做!