Spring MVC视图解析器
Spring MVC的视图解析器
什么是视图解析器
在我们学习Spring MVC时,当我们发送请求给Spring MVC控制的资源时被DispatcherServlet处理,Spring会分析看所有的HandleMapping中定义的请求映射中最合理的那个Handle,并通过HandleMapping得到该Handle,交给HandleAdapter处理Handle并返回一个ModelAndView对象。获取到ModelAndView对象后,Spring会把View渲染交给用户。在渲染View的过程中,发挥作用的就是ViewResolver和View。有时ModelAndView中不包含真正的视图,而是一个逻辑视图名的时候,ViewResolver就会根据规则将逻辑视图名解析成真正的View对象,View对象才是真正进行渲染的,把结果返回给浏览器。
通过上面的流程解释,我们直到ViewResolver
和View
是Spring MVC视图解析的中最重要的接口。Spring MVC给我们提供了非常多的视图解析器,我们可以先讲一下什么是视图?
View接口的重要实现类
视图基础接口,它的各种实现类是无状态的,因此是线程安全的
在此简单介绍一下其中两个重要的View实现类:
其中的renderMergedOutputModel是AbstractView抽象类中的重写方法,实际上就是给View的render提供服务,render才是View对象的渲染。
重定向:RedirectView
这个视图跟重定向有关,也是重定向问题的核心。
首先通过源码看渲染过程:
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws IOException {
String targetUrl = this.createTargetUrl(model, request);
targetUrl = this.updateTargetUrl(targetUrl, model, request, response);
FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);
if (!CollectionUtils.isEmpty(flashMap)) {
UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build();
flashMap.setTargetRequestPath(uriComponents.getPath());
flashMap.addTargetRequestParams(uriComponents.getQueryParams());
FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request);
if (flashMapManager == null) {
throw new IllegalStateException("FlashMapManager not found despite output FlashMap having been set");
}
flashMapManager.saveOutputFlashMap(flashMap, request, response);
}
this.sendRedirect(request, response, targetUrl, this.http10Compatible);
}
看到String targetUrl = this.createTargetUrl(model, request);
这个作用就是创建Url重定向的路径。
最重要的来了,看到最后一行this.sendRedirect(request, response, targetUrl, this.http10Compatible);
,原来最后是使用了sendRedirect重定向到targetUrl
。
其中还有重要的就是构造路径的过程,有兴趣可以去研究源码,有需求可以在重定向时传递数据,在url中显示,这个就是在构造路径中实现的。
请求转发:InternalResourceView
查看渲染过程:
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
this.exposeModelAsRequestAttributes(model, request);
this.exposeHelpers(request);
String dispatcherPath = this.prepareForRendering(request, response);
RequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + this.getUrl( ) + "]: Check that the corresponding file exists within your web application archive!");
} else {
if (this.useInclude(request, response)) {
response.setContentType(this.getContentType());
if (this.logger.isDebugEnabled()) {
this.logger.debug("Including resource [" + this.getUrl() + "] in InternalResourceView '" + this.getBeanName() + "'");
}
rd.include(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Forwarding to resource [" + this.getUrl() + "] in InternalResourceView '" + this.getBeanName() + "'");
}
rd.forward(request, response);
}
}
}
咱们暂时只要关注两个点即可rd.include(request, response);
和rd.forward(request, response);
这个两个方法,根据判断条件来决定执行哪个方法到指定的jsp页面。
ViewResolver视图解释器接口
ViewResolver接口中只有一个方法:
View resolveViewName(String viewName, Locale locale) throws Exception;
根据视图名称viewName和Local对象来获取View对象,这就是视图解释器的作用。
那我们来解释ViewResolver接口的一个常用的实现类InternalResourceViewResolver
InternalResourceViewResolver
继承自UrlBasedViewResolver,以InternalResourceView作为视图,若项目中存在“javax.servlet.jsp.jstl.core.Config”该类,那么会以JstlView作为视图。重写了buildView方法,主要就是为了给InternalResourceView视图设置属性。
所以我们从中得知InternalResourceViewResolver是以InternalResourceView作为视图也就是说它只能处理请求转发,处理不了重定向! 然后我们再看看UrlBasedViewResolver
是什么?
UrlBasedViewResolver
UrlBasedViewResolver继承自AbstractCachingViewResolver抽象类、并实现Ordered接口的类
AbstractCachingViewResolver抽象类:
- AbstractCachingViewResolver是一个抽象类,这种视图解析器会把它曾经解析过的视图保存起来,然后每次要解析视图的时候先从缓存里面找,如果找到了对应的视图就直接返回,如果没有就创建一个新的视图对象,然后把它放到一个用于缓存的map中,接着再把新建的视图返回。使用这种视图缓存的方式可以把解析视图的性能问题降到最低。
- AbstractCachingViewResolver是带有缓存功能的ViewResolver接口基础实现抽象类,该类有个属性名为viewAccessCache的以 "viewName_locale" 为key, View接口为value的Map。该抽象类实现的resolveViewName方法内部会调用createView方法,方法内部会调用loadView抽象方法。
Ordered接口
实现了相同接口的实现类优先级问题,也就是将相同接口的实现类进行排序。后期学习工作原理的时候就会理解透彻。
OK了解UrlBasedViewResolver过后,我们再来看看他的属性和方法
- viewClass 视图的类型
- prefix 视图名称的前缀
- suffix 视图名称的后缀
这三属性比较重要,视图的类型能理解,但是前缀和后缀是什么意思?
这个时候就能体现UrlBasedViewResolver的重要性了,当我们根据请求得到ModelAndView时,可能只包含的逻辑路径并不是完整的,比如真实路径是/a/b/c/d/e.jsp
,但是指定viewName时却是“e”,这个时候UrlBasedViewResolver的前缀后缀就发挥作用了:前缀+viewName+后缀,所以前缀就可以设置为/a/b/c/d/
而后缀就可以设为.jsp
这样就解决了这个问题,同时在遇到多个jsp文件放在WEB-INF
的多级目录下,这个时候作用很大,节省了代码,但是必须指定viewClass(视图类型)。
可以通过看源码的createView()
核心方法来理解产生View的原理
大致一眼看去主要产生的View的类型有两种,一个是以"redirect:"为前缀,类型是RedirectView。另一种是以"forward:"为前缀,类型是InternalResourceView。
而我们会常用InternalResourceViewResolver这个视图解析器,它继承了UrlBasedViewResolver,看初始化的源码:
其中无参构造器中设置了Class<?> viewClass=this.requiredViewClass();
protected Class<?> requiredViewClass() {
return InternalResourceView.class;
}
不难看出InternalResourceViewResolver已经规定了它只能处理InternalResourceView类型的视图,也就是只能解析请求转发,而重定向则不能。有参构造也就是设置了前缀和后缀。使用InternalResourceViewResolver的案例就不演示了,网上一大把。就是记录一下学习视图解析器的笔记。
总结
视图解析器是Spring MVC为之重要的一个环节,主要是依靠ViewResolver
和View
- ViewResolver根据ModelAndView的逻辑视图名解析出View对象
- View则将视图渲染返回给浏览器呈现给用户
视图解析器的表层含义也就理解到这里,更深层次的理解也需要更深入源码。有什么错误请指出。