SpringMVC源码总结 ViewResolver介绍

2023-11-07

首先我们先看看ModelAndView中重要的View接口。 

View接口: 

Java代码   收藏代码
  1. String getContentType();  
  2.   
  3.     /** 
  4.      * Render the view given the specified model. 
  5.      * <p>The first step will be preparing the request: In the JSP case, 
  6.      * this would mean setting model objects as request attributes. 
  7.      * The second step will be the actual rendering of the view, 
  8.      * for example including the JSP via a RequestDispatcher. 
  9.      * @param model Map with name Strings as keys and corresponding model 
  10.      * objects as values (Map can also be {@code null} in case of empty model) 
  11.      * @param request current HTTP request 
  12.      * @param response HTTP response we are building 
  13.      * @throws Exception if rendering failed 
  14.      */  
  15. //上面说的很清楚,对于jsp来说,第一步就是将model作为request的attributes;第二步才开始渲染view  
  16.     void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;  

再看下ViewResolver接口:  
Java代码   收藏代码
  1. View resolveViewName(String viewName, Locale locale) throws Exception;  

它是对给定的viewName找到对应的View对象,然后使用该view对象的render方法将本身的内容写到response中。 
然后就看下,当我们的处理函数返回一个viewName时,SpringMVC是如何渲染的。  
Java代码   收藏代码
  1. try {  
  2.                     // Actually invoke the handler.  
  3.                     mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  
  4.                 }  
  5.                 finally {  
  6.                     if (asyncManager.isConcurrentHandlingStarted()) {  
  7.                         return;  
  8.                     }  
  9.                 }  
  10.   
  11.                 applyDefaultViewName(request, mv);  
  12.                 mappedHandler.applyPostHandle(processedRequest, response, mv);  
  13.             }  
  14.             catch (Exception ex) {  
  15.                 dispatchException = ex;  
  16.             }  
  17. //这里是我们的关注重点,就是进行视图渲染的过程  
  18.             processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);  
  19.         }  
  20.         catch (Exception ex) {  
  21.             triggerAfterCompletion(processedRequest, response, mappedHandler, ex);  
  22.         }  
  23.         catch (Error err) {  
  24.             triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);  
  25.         }  

继续看下processDispatchResult是如何来渲染的  
Java代码   收藏代码
  1. private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,  
  2.             HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {  
  3.   
  4.         boolean errorView = false;  
  5.   
  6.         if (exception != null) {  
  7.             if (exception instanceof ModelAndViewDefiningException) {  
  8.                 logger.debug("ModelAndViewDefiningException encountered", exception);  
  9.                 mv = ((ModelAndViewDefiningException) exception).getModelAndView();  
  10.             }  
  11.             else {  
  12.                 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);  
  13.                 mv = processHandlerException(request, response, handler, exception);  
  14.                 errorView = (mv != null);  
  15.             }  
  16.         }  
  17.   
  18.         // Did the handler return a view to render?  
  19. //这里是我们关注的重点  
  20.         if (mv != null && !mv.wasCleared()) {  
  21.             render(mv, request, response);  
  22.             if (errorView) {  
  23.                 WebUtils.clearErrorRequestAttributes(request);  
  24.             }  
  25.         }  
  26.         else {  
  27.             if (logger.isDebugEnabled()) {  
  28.                 logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +  
  29.                         "': assuming HandlerAdapter completed request handling");  
  30.             }  
  31.         }  
  32.   
  33.         if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {  
  34.             // Concurrent handling started during a forward  
  35.             return;  
  36.         }  
  37.   
  38.         if (mappedHandler != null) {  
  39.             mappedHandler.triggerAfterCompletion(request, response, null);  
  40.         }  
  41.     }  

Java代码   收藏代码
  1. protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {  
  2.         // Determine locale for request and apply it to the response.  
  3.         Locale locale = this.localeResolver.resolveLocale(request);  
  4.         response.setLocale(locale);  
  5.   
  6.         View view;  
  7.         if (mv.isReference()) {  
  8.             // We need to resolve the view name.  
  9.             view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);  
  10.             if (view == null) {  
  11.                 throw new ServletException("Could not resolve view with name '" + mv.getViewName() +  
  12.                         "' in servlet with name '" + getServletName() + "'");  
  13.             }  
  14.         }  
  15.         else {  
  16.             // No need to lookup: the ModelAndView object contains the actual View object.  
  17.             view = mv.getView();  
  18.             if (view == null) {  
  19.                 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +  
  20.                         "View object in servlet with name '" + getServletName() + "'");  
  21.             }  
  22.         }  
  23.   
  24.         // Delegate to the View object for rendering.  
  25.         if (logger.isDebugEnabled()) {  
  26.             logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");  
  27.         }  
  28.         try {  
  29.             view.render(mv.getModelInternal(), request, response);  
  30.         }  
  31.         catch (Exception ex) {  
  32.             if (logger.isDebugEnabled()) {  
  33.                 logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +  
  34.                         getServletName() + "'", ex);  
  35.             }  
  36.             throw ex;  
  37.         }  
  38.     }  

这里可以看到整体的处理流程。首先判断view是不是一个视图的名称,若是需要找到这个视图名称对应的View对象,然后便是调用view对象的render方法,渲染到response中。 
由于我们的处理函数经常仅仅是返回一个view名称,所以我们重点要看看它是如何根据视图名称来找到对应的View对象的,即resolveViewName方法内容。其实上文已经说明了View接口和ViewResolver 接口,ViewResolver 接口就是根据view名称来找到对应的View对象的,所以看下面就会很清晰明白  
Java代码   收藏代码
  1. protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,  
  2.             HttpServletRequest request) throws Exception {  
  3.   
  4.         for (ViewResolver viewResolver : this.viewResolvers) {  
  5.             View view = viewResolver.resolveViewName(viewName, locale);  
  6.             if (view != null) {  
  7.                 return view;  
  8.             }  
  9.         }  
  10.         return null;  
  11.     }  

这里就是对DispatcherServlet的private List<ViewResolver> viewResolvers属性进行遍历找到一个能够获取View对象的ViewResolver,并返回这个view对象。 
至此整个流程便走通了,接下来就是要看看有哪些ViewResolver以及它们的注册来源是什么? 

常用的ViewResolver有:FreeMarkerViewResolver、InternalResourceViewResolver、VelocityViewResolver等。 

接下来就是如何来注册这些ViewResolver:  
Java代码   收藏代码
  1. protected void initStrategies(ApplicationContext context) {  
  2.         initMultipartResolver(context);  
  3.         initLocaleResolver(context);  
  4.         initThemeResolver(context);  
  5.         initHandlerMappings(context);  
  6.         initHandlerAdapters(context);  
  7.         initHandlerExceptionResolvers(context);  
  8.         initRequestToViewNameTranslator(context);  
  9. //我们关注的重点  
  10.         initViewResolvers(context);  
  11.         initFlashMapManager(context);  
  12.     }  

还是在DispatcherServlet的初始化策略中,调用了initViewResolvers,如下:  
Java代码   收藏代码
  1. private void initViewResolvers(ApplicationContext context) {  
  2.         this.viewResolvers = null;  
  3.   
  4.         if (this.detectAllViewResolvers) {  
  5.             // Find all ViewResolvers in the ApplicationContext, including ancestor contexts.  
  6.             Map<String, ViewResolver> matchingBeans =  
  7.                     BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.classtruefalse);  
  8.             if (!matchingBeans.isEmpty()) {  
  9.                 this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());  
  10.                 // We keep ViewResolvers in sorted order.  
  11.                 OrderComparator.sort(this.viewResolvers);  
  12.             }  
  13.         }  
  14.         else {  
  15.             try {  
  16.                 ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);  
  17.                 this.viewResolvers = Collections.singletonList(vr);  
  18.             }  
  19.             catch (NoSuchBeanDefinitionException ex) {  
  20.                 // Ignore, we'll add a default ViewResolver later.  
  21.             }  
  22.         }  
  23.   
  24.         // Ensure we have at least one ViewResolver, by registering  
  25.         // a default ViewResolver if no other resolvers are found.  
  26.         if (this.viewResolvers == null) {  
  27.             this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);  
  28.             if (logger.isDebugEnabled()) {  
  29.                 logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");  
  30.             }  
  31.         }  
  32.     }  

这和HandleMapping和HandlerAdapter的初始化过程基本类似。this.detectAllViewResolvers是DispatcherServlet的一个boolean属性,可以在web.xml文件中修改这个值,默认是true。  
Java代码   收藏代码
  1. /** Detect all ViewResolvers or just expect "viewResolver" bean? */  
  2.     private boolean detectAllViewResolvers = true;  

当detectAllViewResolvers为true,意味着就会获取从xml文件中解析出来的ViewResolver。如果为false,则直接去找bean name为"viewResolver"并且是ViewResolver类型的作为DispatcherServlet的ViewResolver。 
当上述两种情况都没有找到,则会启用默认的ViewResolver,在this.viewResolvers = getDefaultStrategies(context, ViewResolver.class)中,这个过程已经多次说过,可以见本系列第一篇HandleMapping的来源。它就是依据DispatcherServlet.properties文件中所配置的ViewResolver,如下:  
Java代码   收藏代码
  1. org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver  

也就是默认采用的是InternalResourceViewResolver。 
再说说在xml文件中配置ViewResolver的情况,如下:  
Java代码   收藏代码
  1. <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">  
  2.         <property name="templateLoaderPath" value="/WEB-INF/views" />  
  3.         <property name="defaultEncoding" value="utf-8" />  
  4.         <property name="freemarkerSettings">  
  5.             <props>  
  6.                 <prop key="locale">zh_CN</prop>  
  7.             </props>  
  8.         </property>  
  9.     </bean>  
  10.     <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">  
  11.         <property name="suffix" value=".html" />  
  12.         <property name="contentType" value="text/html;charset=utf-8" />  
  13.         <property name="requestContextAttribute" value="request" />  
  14.         <property name="exposeRequestAttributes" value="true" />  
  15.         <property name="exposeSessionAttributes" value="true" />  
  16.     </bean>  

这里是以FreeMarkerViewResolver为例来说明,它的配置内容还是需要有待继续研究。这里只是粗略的说下它的继承情况。 
FreeMarkerViewResolver继承AbstractTemplateViewResolver继承UrlBasedViewResolver继承AbstractCachingViewResolver。 
首先是抽象类AbstractCachingViewResolver:它加入了缓存功能,它有几个重要的属性。  
Java代码   收藏代码
  1. /** Default maximum number of entries for the view cache: 1024 */  
  2.     public static final int DEFAULT_CACHE_LIMIT = 1024;  
  3.   
  4.     /** The maximum number of entries in the cache */  
  5.     private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;  
  6.   
  7.       
  8.   
  9.     /** Fast access cache for Views, returning already cached instances without a global lock */  
  10.     private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT);  
  11.   
  12.     /** Map nbsp;from view key to View instance, synchronized for View creation */  
  13.     @SuppressWarnings("serial")  
  14.     private final Map<Object, View> viewCreationCache =  
  15.             new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {  
  16.                 @Override  
  17.                 protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {  
  18.                     if (size() > getCacheLimit()) {  
  19.                         viewAccessCache.remove(eldest.getKey());  
  20.                         return true;  
  21.                     }  
  22.                     else {  
  23.                         return false;  
  24.                     }  
  25.                 }  
  26.             };  

属性一:cacheLimit 最大的缓存数量,默认为1024。 
属性二:viewAccessCache 是ConcurrentHashMap类型的,适合高并发。 
属性三:viewCreationCache是LinkedHashMap类型的 
我们再来看下,由view名称来解析到view视图对象的具体过程:  
Java代码   收藏代码
  1. public View resolveViewName(String viewName, Locale locale) throws Exception {  
  2. //这里进行了是否进行缓存的判断,即cacheLimit是否大于0  
  3.         if (!isCache()) {  
  4.                         //不进行缓存,始终每次都创建  
  5.             return createView(viewName, locale);  
  6.         }  
  7.         else {  
  8.                         //viewAccessCache viewCreationCache两者的key  
  9.             Object cacheKey = getCacheKey(viewName, locale);  
  10.             View view = this.viewAccessCache.get(cacheKey);  
  11.             if (view == null) {  
  12.                 synchronized (this.viewCreationCache) {  
  13.                     view = this.viewCreationCache.get(cacheKey);  
  14.                     if (view == null) {  
  15.                         // Ask the subclass to create the View object.  
  16.                         view = createView(viewName, locale);  
  17.                         if (view == null && this.cacheUnresolved) {  
  18.                             view = UNRESOLVED_VIEW;  
  19.                         }  
  20.                         if (view != null) {  
  21.                             this.viewAccessCache.put(cacheKey, view);  
  22.                             this.viewCreationCache.put(cacheKey, view);  
  23.                             if (logger.isTraceEnabled()) {  
  24.                                 logger.trace("Cached view [" + cacheKey + "]");  
  25.                             }  
  26.                         }  
  27.                     }  
  28.                 }  
  29.             }  
  30.             return (view != UNRESOLVED_VIEW ? view : null);  
  31.         }  
  32.     }  

对于Object cacheKey = getCacheKey(viewName, locale);默认为viewName + "_" + locale; 
但是可以被子类覆盖,子类UrlBasedViewResolver覆盖了它,变成只有viewName。 

先从viewAccessCache中看能否找到已缓存的view视图,若能找到则返回。若未找到则加上同步锁synchronized (this.viewCreationCache),进入这个方法之后,最关键的是仍需要进行一次判断view = this.viewCreationCache.get(cacheKey),看看是否已经创建过了,并不是viewAccessCache和viewCreationCache他们所缓存的内容不一样而是如果没有这个判断,则会有多线程问题。 

如线程1和线程2同时要解析相同的view名称,他们都来到同步锁synchronized (this.viewCreationCache)之前,线程2先拿到锁,线程1等待,线程2创建好view视图后,加入viewCreationCache和viewAccessCache,并释放锁。此时线程1获得锁,进入同步锁synchronized (this.viewCreationCache)内部,若不进行判断,则线程1又会去创建一次view视图。所以view = this.viewCreationCache.get(cacheKey)并判断view是否为null这一步骤是十分有用的。 

创建View视图的任务就交给了子类来实现。resolveViewName这个方法基本上就分析完了,应该还会想到,它的那个cacheLimit限制好像还没发挥出作用。 
继续回看  
Java代码   收藏代码
  1. private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<Object, View>(DEFAULT_CACHE_LIMIT);  
  2.   
  3.     private final Map<Object, View> viewCreationCache =  
  4.             new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {  
  5.                 @Override  
  6.                 protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {  
  7.                     if (size() > getCacheLimit()) {  
  8.                         viewAccessCache.remove(eldest.getKey());  
  9.                         return true;  
  10.                     }  
  11.                     else {  
  12.                         return false;  
  13.                     }  
  14.                 }  
  15.             };  

viewCreationCache 的类型是LinkedHashMap,但是它复写了protected boolean removeEldestEntry(Map.Entry<Object, View> eldest)方法,当该方法返回true时,LinkedHashMap则会删除最老的key。在这里我们可以看到,当viewCreationCache 的所存的View数量达到cacheLimit时,就会删除最老的那个key和value,同时也会使viewAccessCache删除这个key和value。 

viewAccessCache主要是用来高并发的访问,viewCreationCache 则是用来统计最老的key。他们所存储的view都是一样的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringMVC源码总结 ViewResolver介绍 的相关文章

随机推荐

  • 项目实训(树莓派)(七)树莓派4B下的ubuntu系统下命令行的使用-磁盘管理部分

    目录 前言 实验目的 实验内容 实验环境 实验步骤 1 df命令 2 fdisk命令 磁盘分区 3 hdparm命令 显示与设定硬盘参数 4 lsblk命令 查看系统的磁盘 5 vgextend命令 扩展卷组 前言 通过前面的实验 我们已经
  • 【MySQL笔记】正确的理解MySQL的MVCC及实现原理

    MVCC多版本并发控制 如果觉得对你有帮助 能否点个赞或关个注 以示鼓励笔者呢 博客目录 先点这里 首先声明 MySQL 的测试环境是 5 7 前提概要 什么是 MVCC 什么是当前读和快照读 当前读 快照读和 MVCC 的关系 MVCC
  • 数字水印技术

    数字水印技术在信息安全中属于数字版权保护方面的技术 数字水印通过嵌入或附加数字信息到数字媒体中 可以追踪和证明数字媒体的来源 版权 真伪等 数字水印可以被用于防止盗版 保护知识产权 证明数字证据的真实性等应用场景 从而保障信息安全和维护合法
  • jQuery学习

    1 jQuery概述 1 1 JavaScript 库 JavaScript 库 即library 是一个封装好的特定的集合 方法和函数 从封装一大堆函数的角度理解库 就是在这个库中 封装了很多预先定义好的函数在里面 比如动画animate
  • BAJT 中高级 Java 面试题答案

    1 请问你做过哪些JVM优化 使用什么方法达到什么效果 vm调优主要也就是内存空间的分配 最终策略 提高系统性能 主要策略有 1 增加eden空间 让更多的对象留在年轻代 2 大对象直接放到老年代 以免扰乱年轻代高频率的gc XX Pete
  • 怎么查找电脑中的流氓软件_1个神器彻底删除流氓软件,瞬间清出十几个G,你的电脑有救了!...

    在使用电脑中 我们总会遇到一些流氓软件 不仅严重拖慢了电脑的运行速度 还不定时地弹出一个又一个的 定时炸弹 毋庸置疑 这些定时炸弹就是各种烦人的广告和弹窗了 尽管我们通过常规的方式把它卸载 但它还会卷土重来 那么对于那些卸载了仍会有文件残留
  • SpringCloud:SpringCloud生态的组成,组件的介绍(一)

    springCloud官方文档 https www springcloud cc 中文网 https spring io projects spring cloud 官方网 SpringCloud是什么 Spring Cloud是一个基于S
  • 一文读懂:区块链中的Merkle树

    我们知道 区块链中每个区块包括区块头和区块体两部分 个人技术公众号 解决方案工程师 欢迎同领域的朋友关注 相互交流 像在CSDN一样 分享技术 分享代码 分享方案文档 分享白皮书 区块体中包含了由区块链系统产生的一系列交易数据 并以Merk
  • SLAM入门

    SLAM定义 SLAM Simultaneous localization and mapping 同时定位 我在哪里 与建图 我周围有什么 当某种移动设备 汽车 扫地机 手机 无人机 机器人 从一个未知环境的未知地点出发 在运动过程中 通
  • P27 多表查询的分类:非等值连接、自连接、内、外连接

    3 多表查询的分类 7 多表查询的分类 角度1 等值连接 vs 非等值连接 角度2 自连接 vs 非自连接 角度3 内连接 vs 外连接 等值连接 vs 非等值连接 SELECT FROM job grades 非等值连接 薪资是在一个范围
  • airpods固件更新方法_AirPods2/AirPods Pro新固件怎么升级 固件更新方法

    17日上午 苹果公司发布了针对 AirPods 2 和 AirPods Pro 两款无线耳机的的固件更新 不过目前官方并未说明此次更新的具体改进 AirPods Pro 是苹果 10 月底推出的新品 支持主动降噪功能 在今天之前 它的固件版
  • MySQL数据库基本概念介绍

    MySQL数据库 一 数据库的简介 1 数据 Data 2 表 3 数据库 二 数据库的概念 1 数据库管理系统 DBMS 2 数据库系统 三 数据库的发展史 1 第一代数据库 2 第二代数据库 3 第三代数据库 四 当前主流数据库介绍 1
  • 搜索引擎solr系列---与java的springboot项目连接配置

    java与solr连接 调用查询的方式 我知道的有两种 solrj方式 这种方式写法较麻烦 倒不是因为难 就是简单的逻辑 有时候为了一个业务写一堆代码 所以solrj的这种方式还是比较灵活的 能实现你需要的变态业务需求 我发现它的一个小缺点
  • SpringBoot 3.x整合Fluent Mybatis极简流程

    此为基础配置 不包括其他高级配置 需要其他高级配置请查阅官方文档 fluent mybatis特性总览 Wiki Gitee com https gitee com fluent mybatis fluent mybatis wikis f
  • 软件测试学习路线

    下图是某培训机构的课程概要 同样的 我们学习的路线基本如此 下面主要总结一下 注意 因为自身原因 所以我的方案是自己的自学方案 仅作参考 1 测试基础知识 一些测试必备文档以及概念要掌握 这是最基本的 1 gt 测试分类 按测试技术划分为
  • 实验吧——加了料的报错注入

    coding utf8 import requests import re def denglu username password 设置代理 用于调试过程中抓包分析 proxies http http localhost 9008 htt
  • 了解文件的随机读写,文件类别、文件缓冲区,文件操作知识点补充(接上文)

    文件的操作 老规矩笔记自取 文件操作进阶笔记 欢迎喜欢学习C C 的朋友互关一起努力 文章目录 文件的操作 一 文件的随机读写 1 fseek 定位文件指针函数 2 ftell 当前偏移量函数 3 rewind 返回起始位置函数 二 文本文
  • java操作seaweedfs

    前置条件是seaweedfs服务已成功启动 具体部署可参考我上篇文章SeaweedFS部署及使用指南 首先导入pom依赖
  • Python Scrapy网络爬虫框架从入门到实战

    Python Scrapy是一个强大的网络爬虫框架 它提供了丰富的功能和灵活的扩展性 使得爬取网页数据变得简单高效 本文将介绍Scrapy框架的基本概念 用法和实际案例 帮助你快速上手和应用Scrapy进行数据抓取 Scrapy是一个基于P
  • SpringMVC源码总结 ViewResolver介绍

    首先我们先看看ModelAndView中重要的View接口 View接口 Java代码 String getContentType Render the view given the specified model p The first