视图层

forward: 转发

和原本的 SpringMVC 一样,写在方法的返回值处,不过不同的是 forward: 不会被拼串,并且是以项目地址为相对路径的。

原:

1
2
3
4
5
@RequestMapping("/TestRequest/")
public ModelAndView test() {
ModelAndView requestScope = new ModelAndView("hello");
return requestScope;
}

forward:

1
2
3
4
5
@RequestMapping("/TestRequest/")
public ModelAndView test() {
ModelAndView requestScope = new ModelAndView("forward:/WEB-INF/pages/hello.jsp");
return requestScope;
}

redirect: 重定向

使用方法和 forward: 转发是一样的,和使用 response.sendRedirect("/XXX") 唯一不同的是,相对地址会自动加上项目地址,方便浏览器解析。

1
2
3
4
5
@RequestMapping("/TestRequest/")
public ModelAndView test() {
ModelAndView requestScope = new ModelAndView("redirect:/WEB-INF/pages/hello.jsp");
return requestScope;
}

视图解析原理

这一段转自与知乎,其实自己也写了总结,但是觉得不太行,没有下文理解的清晰。

  1. 当我们对 SpringMVC 控制的资源发起请求时,这些请求都会被 SpringMVC 的 org.springframework.web.servlet.DispatcherServlet 处理,接着 SpringMVC 会分析看哪一个 HandlerMapping 定义的请求映射中存在对该请求的最合理的映射。然后通过该 HandlerMapping 取得其对应的 Handler(也就是我们定义的处理请求方法),接着再通过相应的 HandlerAdapter 处理该 Handler。HandlerAdapter 再对Handler 进行处理。

  2. 之后会返回一个 ModelAndView 对象。在获得了 ModelAndView 对象之后,Spring 就需要把该 View 渲染给用户,即返回给浏览器。在这个渲染的过程中,发挥作用的就是 ViewResolver 和 View。当 Handler 返回的 ModelAndView 中不包含真正的视图,只返回一个逻辑视图名称,ViewResolver 会把该逻辑视图名称解析为真正的视图 View 对象。View 真正进行视图渲染,把结果返回给浏览器。

而 ViewResolver 和 View 都是一个接口,定义如下:

1
2
3
public interface ViewResolver {
View resolveViewName(String var1, Locale var2) throws Exception;
} //用于解析 ModelAndView 里面保存的 viewName 然后根据解析结果生成一个合适的 View 实现对象。
1
2
3
public interface View {
void render(Map<String, ?> var1, HttpServletRequest var2, HttpServletResponse var3) throws Exception;
} //实现 View 接口只需要实现 render() 方法即可,render 渲染,将获取的数据和展示模板一起渲染成一个用户收到的页面。

调用 View 对象的 render 渲染(指生成用户看到的 html 页面)方法,进行请求转发(并将模型输入放入 Request 域中),或者请求重定向。SpringMVC 主要负责的是生成 View 的过程,至于不同的 View 接口实现可以五花八门,实现了视图层的解耦。

使用 JstlView 实现国际化

在 Spring 中导入 taglibs-standard-impl 和 taglibs-standard-spec 这两个包之后,在 Spring 中配置的 InternalResourceViewResolver 中,添加 viewClass 属性值为 JstlView 之后,ViewResolver 将生成的 View 实例从原本的 InternalResourceView 更改为更强大的子类:JstlView。

这个 View 强大之处就是大大简化了国际化的配置与使用。

首先搞几个不同国家地区的文字:i18n_en_US.propertiesi18n_zh_CN.properties 。然后在 Spring 中配置:

1
2
3
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n" />
</bean>

需要注意 id 一定要是 messageSource ,Spring 是根据这个 id 名字来寻找绑定的国际化信息的。然后在相关的 jsp 页面引入 fmt 标签,然后使用 <fmt:message> 来输出国际化信息。

1
2
3
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<fmt:message key="username"/>
<fmt:message key="password"/>

请求页面跳转访问

因为很多强大的功能只有经过 SpringMVC 才能实现,比如上面的国际化,这就需要我们对一些直接访问的资源经过 SpringMVC 处理,或者访问 WEB-INF 下面的资源,也需要进行请求转发才可以获取资源。那么大量的仅仅只进行请求转发的处理方法写起来太乱了,可以直接用一条 Spring 的配置解决。

1
2
3
4
@RequestMapping("/Login")
public String toLogin() {
return "Login";
}

SpringMVC 支持通过配置的方法,来实现访问地址直接向 WEB-INF 下资源的访问:

1
2
<mvc:view-controller path="/login" view-name="Login" />
<mvc:annotation-driven/>

但是需要注意的是,使用了 mvc:view-controller 来接管对请求路径的访问之后,会打断原本注解标注的 @RequestMapping,需要额外添加一个 mvc:annotation-driven 来进行扫描注解接管请求映射。

自定义视图解析器

大致过程:首先实现 ViewResolver 和 View 两个接口。ViewResolver 还需要实现 Ordered ,来将优先级提高到高于 InternalResourceViewResolver,只需要获取的 order 值越低,权重越高!

如果开头是 Tempest ,则确认为本 ViewResolver 处理对象,返回一个本 ViewResolver 生成的 View 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyViewResolver implements ViewResolver, Ordered {

private int order;

@Override
public View resolveViewName(String s, Locale locale) throws Exception {
if(s.startsWith("Tempest:")) {
return new MyView(s.substring(8));
} else return null;
}

public void setOrder(int order) { // 这个 order 值是在 Spring 的配置文件里面设置的
this.order=order;
}

@Override
public int getOrder() {
return order;
}
}

这个 View 的渲染方式就是转发请求到指定的页面中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyView implements View {
private String address;

MyView(String address) {
this.address=address;
}
@Override
public String getContentType() {
return "text/html";
}

@Override
public void render(Map<String, ?> map, HttpServletRequest request, HttpServletResponse response) throws Exception {
request.getRequestDispatcher("/WEB-INF/pages/"+address+".jsp").forward(request, response);
}
}

然后记得将自己实现的 ViewResolver 添加到 IOC 容器(配置文件注册 bean 的 class 即可)中,Spring 会根据是否实现 ViewResolver 接口决定此类是否为视图解析类,并在控制 View 生成的时候,依次调用所有的 ViewResolver 的 resolveViewName() 方法。所以必须让 Spring 知道自己才有可能被调用去生成 View。

1
2
3
<bean class="space.xorex.SpringMVC.ViewResolver.MyViewResolver">
<property name="order" value="1"/>
</bean>

然后我们再进行请求映射:

1
2
3
4
@RequestMapping("/test/")
public String test() {
return "Tempest:hello";
}

这样当我们发起 XXXX/test/ 的请求的时候,就会将 Tempest:hello 视图名根据 order 权重依次传入 ViewResolver 的 resolveViewName() 中,我们写的 ViewResolver 看到了 Tempest: 的开头,匹配成功并返回我们自己实现的 View:MyView,最后调用 MyView 的渲染方法 render() 并执行我们指定的请求转发代码。