Spring MVC视图解析器

1,605 阅读5分钟

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对象才是真正进行渲染的,把结果返回给浏览器。

通过上面的流程解释,我们直到ViewResolverView是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抽象类:

  1. AbstractCachingViewResolver是一个抽象类,这种视图解析器会把它曾经解析过的视图保存起来,然后每次要解析视图的时候先从缓存里面找,如果找到了对应的视图就直接返回,如果没有就创建一个新的视图对象,然后把它放到一个用于缓存的map中,接着再把新建的视图返回。使用这种视图缓存的方式可以把解析视图的性能问题降到最低。
  2. 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的原理 20210711204826 大致一眼看去主要产生的View的类型有两种,一个是以"redirect:"为前缀,类型是RedirectView。另一种是以"forward:"为前缀,类型是InternalResourceView。


而我们会常用InternalResourceViewResolver这个视图解析器,它继承了UrlBasedViewResolver,看初始化的源码: 20210711205539 其中无参构造器中设置了Class<?> viewClass=this.requiredViewClass();

protected Class<?> requiredViewClass() {
    return InternalResourceView.class;
}

不难看出InternalResourceViewResolver已经规定了它只能处理InternalResourceView类型的视图,也就是只能解析请求转发,而重定向则不能。有参构造也就是设置了前缀和后缀。使用InternalResourceViewResolver的案例就不演示了,网上一大把。就是记录一下学习视图解析器的笔记。

总结

视图解析器是Spring MVC为之重要的一个环节,主要是依靠ViewResolverView

  • ViewResolver根据ModelAndView的逻辑视图名解析出View对象
  • View则将视图渲染返回给浏览器呈现给用户

视图解析器的表层含义也就理解到这里,更深层次的理解也需要更深入源码。有什么错误请指出。