• Spring MVC工作原理及源码解析(四) ViewResolver实现原理及源码解析


    0、ViewResolver原理介绍

    根据视图的名称将其解析为 View 类型的视图,如通过 ModelAndView 中的视图名称将其解析成 View,View 是用来渲染页面的,也就是将 Model 填入模板中,生成 html 或其他格式的文件。
    可以设置多个解析策略,如可以根据 JSP 来解析,或者按照 Velocity 模版解析,如果设置了多个解析策略则可以通过 order 属性来设定其优先级,数值越小优先级越高,前面的视图解析器解析后就不会让后面的继续解析。默认的解析策略是 InternalResourceViewResolver,按照 JSP 页面来解析。ViewResolver 接口中的方法如下:
    • View resolveViewName(String viewName, Locale locale);

    该接口的实现类有AbstractCachingViewResolver、BeanNameViewResolver、ContentNegotiatingViewResolver、StandaloneMockMvcBuilder和ViewResolverComposite。

    1、AbstractCachingViewResolver:实现带缓存的ViewResolver

    来看上图中的第一个实现类:AbstractCachingViewResolver,该类实现了ViewResolver的resolveViewName接口,与其他实现类不同,AbstractCachingViewResolver实现的是带有缓存的ViewResolver。
    当前端控制器请求视图解析器解析 ModelAndView 时,AbstractCachingViewResolver实现的ViewResolver在解析时先从缓存里查找,如果找得到视图就返回,找不到就创建新的视图,具体代码如下所示:
    public View resolveViewName(String viewName, Locale locale) throws Exception {
            // 是否启用缓存,可通过setCache()方法或setCacheLimit()方法开启缓存,是一个ConcurrentHashMap,默认缓存大小1024
            if (!this.isCache()) {
                return this.createView(viewName, locale);
            } else {
                // 得到 view 在缓存中的 key 值
                Object cacheKey = this.getCacheKey(viewName, locale);
                View view = (View)this.viewAccessCache.get(cacheKey);
                // 如果没有找到 view 则创建,采用双重校验的方式进行安全创建
                if (view == null) {
                    synchronized(this.viewCreationCache) {
                        view = (View)this.viewCreationCache.get(cacheKey);
                        if (view == null) {
                            // 具体的创建方式由子类实现
                            view = this.createView(viewName, locale);
                            if (view == null && this.cacheUnresolved) {
                                view = UNRESOLVED_VIEW;
                            }
    
                            if (view != null) {
                                this.viewAccessCache.put(cacheKey, view);
                                this.viewCreationCache.put(cacheKey, view);
                                if (this.logger.isTraceEnabled()) {
                                    this.logger.trace("Cached view [" + cacheKey + "]");
                                }
                            }
                        }
                    }
                }
    
                return view != UNRESOLVED_VIEW ? view : null;
            }
        }    

     1.1、ResourceBundleViewResolver

    使用ResourceBundleViewResolver配置下bean就可以让视图解释器支持解析多种视图,而UrlBasedViewResolver,就只支持解释单一类型的视图。

    ResourceBundleViewResolver 根据 views.properties 文件来解析视图,这个文件位于 classpath 路径下,使用方式如下:
    <bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver">  
        <!-- 设定属性文件名为views -->  
        <property name="basename" value="views"></property>  
    </bean>  

    1.2、XmlViewResolver

    XmlViewResolver 根据 xml 文件来解析视图,使用方式如下:
    <bean class="org.springframework.web.servlet.view.XmlViewResolver">
        <property name="location">
            <value>/WEB-INF/spring-views.xml</value>
        </property>
    </bean>

    1.3、UrlBasedViewResolver

    支持解释单一类型的视图。

    UrlBasedViewResolver 提供了拼接 URL 的方式来解析视图,通过 prefix 属性拼接一个前缀,通过 suffix 属性拼接一个后缀,就得到了视图的 URL。还可以加入 redirect: 与 forword: 前缀,使用 redirect: 前缀会调用 HttpServletResponse对象的 sendRedirect() 方法进行重定向,使用 forword: 前缀会利用 RequestDispatcher的forword 方式跳转到指定的地址。另外,使用时还要指定 viewClass 属性,表示要解析成哪种 View,的使用方式如下:
    <bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">  
       <property name="prefix" value="/WEB-INF/" />  
       <property name="suffix" value=".jsp" />  
       <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>  
    </bean> 

    2、其他的 ViewResolver

    2.1、BeanNameViewResolver

    BeanNameViewResolver 是通过视图名称去容器中获取对应的 view 对象,所以在使用前需要将 view 对象注册到容器中。它没有使用缓存,实现方式如下:

        @Override
        public View resolveViewName(String viewName, Locale locale) throws BeansException {
            ApplicationContext context = getApplicationContext();
            // 根据viewName去容器中查找View对象
            if (!context.containsBean(viewName)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("No matching bean found for view name '" + viewName + "'");
                }
                // Allow for ViewResolver chaining...
                return null;
            }
            if (!context.isTypeMatch(viewName, View.class)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Found matching bean for view name '" + viewName +
                            "' - to be ignored since it does not implement View");
                }
                // Since we're looking into the general ApplicationContext here,
                // let's accept this as a non-match and allow for chaining as well...
                return null;
            }
            return context.getBean(viewName, View.class);
        }

    2.2、ContentNegotiatingViewResolver

    ContentNegotiatingViewResolver本身不解析解析视图,而是用来整合所有的ViewResolver类,每次请求都会遍历所有的ViewResolver,然后找到最合适的处理View,并将其返回。源码如下:

        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
            Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
            // 获取Request的MediaType集合
            List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
            if (requestedMediaTypes != null) {
                // 通过遍历ViewResolver,获取所有符合条件的View
                List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
                // 遍历所有的SmartView,SmartView默认是RedirectView返回
                // 否则,根据MediaType最合适的第一个View返回
                View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
                if (bestView != null) {
                    return bestView;
                }
            }
            if (this.useNotAcceptableStatusCode) {
                if (logger.isDebugEnabled()) {
                    logger.debug("No acceptable view found; returning 406 (Not Acceptable) status code");
                }
                return NOT_ACCEPTABLE_VIEW;
            }
            else {
                logger.debug("No acceptable view found; returning null");
                return null;
            }
        }

    2.3、StandaloneMockMvcBuilder

    StandaloneMockMvcBuilder主要用于单元测试,代码如下所示:

        /**
         * A {@link ViewResolver} that always returns same View.(始终返回同一个View,用于单元测试)
         */
        private static class StaticViewResolver implements ViewResolver {
    
            private final View view;
    
            public StaticViewResolver(View view) {
                this.view = view;
            }
    
            @Override
            public View resolveViewName(String viewName, Locale locale) throws Exception {
                return this.view;
            }
        }

    2.4、ViewResolverComposite

    ViewResolverComposite是包含如上各个ViewResolver的组合类,其resolveViewName方法代码如下:

        @Override
        public View resolveViewName(String viewName, Locale locale) throws Exception {
            for (ViewResolver viewResolver : this.viewResolvers) {
                // 生成View对象
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
            return null;
        }
    作者:blayn
    出处:https://www.cnblogs.com/blayn/
    版权:本文版权归作者和博客园共有
    转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
  • 相关阅读:
    spring 管理struts2的一个问题
    Log4j的使用
    json
    jbpm
    jbpm的开发流程
    HTML5 INPUT新增属性
    JQuery Mobile
    struts2配置中通配符
    2010新的开始,先留个脚印。:)
    EyesBaby1.0使用帮助文档
  • 原文地址:https://www.cnblogs.com/blayn/p/14721860.html
Copyright © 2020-2023  润新知