首页 > 编程语言 >SpringMVC源码(九):无异常View视图解析

SpringMVC源码(九):无异常View视图解析

时间:2023-02-18 20:22:53浏览次数:47  
标签:null viewName SpringMVC 视图 源码 ModelAndView view View

  在MVC请求流程中,处理Controller控制器逻辑后获取到的ModelAndView对象并不能直接返回到浏览器,需要通过对ModelAndView中的View属性做解析获取视图,并用Model属性中的数据完成对视图的渲染再返回浏览器。

1、核心流程图

  

2、核心流程源码分析

  处理ModelAndView返回结果进行View视图渲染,有关处理过程中出现异常的视图处理,在MVC异常处理时统一分析。本文着重对处理过程中无异常,返回结果为String类型的视图解析处理,DispatcherServlet#processDispatchResult() 核心伪代码:

 1 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
 2       @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
 3       @Nullable Exception exception) throws Exception {
 4     
 5    // 标记是否为处理生成异常的ModelAndView对象
 6    boolean errorView = false;
 7     
 8    // 如果请求处理过程中有异常抛出则处理异常
 9    if (exception != null) {
10       // 从ModelAndViewDefiningException中获得ModelAndView对象
11       if (exception instanceof ModelAndViewDefiningException) {
12          logger.debug("ModelAndViewDefiningException encountered", exception);
13          mv = ((ModelAndViewDefiningException) exception).getModelAndView();
14       }
15       // 处理异常,生成ModelAndView对象
16       else {
17          Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
18          mv = processHandlerException(request, response, handler, exception);
19          errorView = (mv != null);
20       }
21    }
22         
23    // 是否进行页面渲染
24    if (mv != null && !mv.wasCleared()) {
25       // 渲染页面
26       render(mv, request, response);
27    }
28 
29    // 发出请求处理完成通知,触发Interceptor的afterCompletion
30    if (mappedHandler != null) {
31       mappedHandler.triggerAfterCompletion(request, response, null);
32    }
33 }

  有关处理过程中出现异常的场景,本节暂不分析。

2.1、是否需要页面渲染

  当前ModelAndView对象不为空,当前ModelAndView对象是否被清理。

  ModelAndView#wasCleared() 核心代码

1 // 当前实例是否被清理标识,通过调用clear()方法,可完成清理,默认为false
2 private boolean cleared = false;
3 
4 // 当前ModelAndView是否被清理判断
5 public boolean wasCleared() {
6    // 判断清理标识及 ModelAndView中的model属性、View属性
7    return (this.cleared && isEmpty());
8 }

  ModelAndView#wasCleared() 核心代码

1 // ModelAndView是否为一个空对象或者model属性、view为空
2 public boolean isEmpty() {
3    return (this.view == null && CollectionUtils.isEmpty(this.model));
4 }

页面渲染条件:

  ModelAndView对象不为空、当前ModelAndView对象未被清理,并且ModelAndView中的model属性、View属性都不为空,

2.2、页面渲染

  页面渲染,DispatcherServlet#render() 核心伪代码:

 1 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
 2    // 解析request中获得Locale对象,并设置到response 中
 3    Locale locale =
 4          (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
 5    response.setLocale(locale);
 6 
 7    // 声明 View 对象
 8    View view;
 9    // 获取ModelAndView中的视图
10    String viewName = mv.getViewName();
11    // 返回视图为字符串类型,通过对视图名称的解析获取视图
12    if (viewName != null) {
13       // 根据 viewName 获得 View 对象
14       view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
15       // 获取不到view,抛出 ServletException 异常
16       if (view == null) {
17          throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
18                "' in servlet with name '" + getServletName() + "'");
19       }
20    }
21    else {
22       // 直接使用 ModelAndView 对象的 View 对象
23       view = mv.getView();
24       // 获取不到view,抛出 ServletException 异常
25       if (view == null) {
26          throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
27                "View object in servlet with name '" + getServletName() + "'");
28       }
29    }
30 
31    // 设置响应的状态码
32    if (mv.getStatus() != null) {
33      response.setStatus(mv.getStatus().value());
34    }
35    // 渲染页面
36    view.render(mv.getModelInternal(), request, response);
37 }

1、解析请求Request获取Locale设置到响应Response中

  语言处理器LocaleResolver为MVC内置的九大组件之一,在源码(三):MVC九大内置组件初始化中已经提到DispatcherServlet#localeResolver属性的初始化,获取默认处理器AcceptHeaderLocaleResolver,这里不再赘述。

 

2、获取视图View

  获取View视图名称,ModelAndView#getViewName() 核心代码

1 // 若未view为字符串类型,则返回view;否则返回null
2 public String getViewName() {
3    return (this.view instanceof String ? (String) this.view : null);
4 }

获取ModelAndView对象中的view属性,对view类型做如下判断

2.1、ModelAndView属性view类型为String

  解析返回的字符串获取View对象,DispatcherServlet#resolveViewName() 核心代码

 1 // 解析视图名称
 2 protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
 3       Locale locale, HttpServletRequest request) throws Exception {
 4    // 视图解析器为空,返回null
 5    if (this.viewResolvers != null) {
 6       // 遍历 ViewResolver 数组
 7       for (ViewResolver viewResolver : this.viewResolvers) {
 8          // 根据 viewName + locale 参数,解析出 View 对象
 9          View view = viewResolver.resolveViewName(viewName, locale);
10          // 解析成功,直接返回 View 对象
11          if (view != null) {
12             return view;
13          }
14       }
15    }
16    return null;
17 }

  在源码(三):MVC九大内置组件初始化中已提及视图解析器ViewResolver的初始化,获取MVC默认的视图解析器InternalResourceViewResolver,类图如下:

  0

  InternalResourceViewResolver对象的构造函数如下,在创建InternalResourceViewResolver对象时,初始化viewClass属性为JstlView.class。

1 public InternalResourceViewResolver() {
2    Class<?> viewClass = requiredViewClass();
3    if (InternalResourceView.class == viewClass && jstlPresent) {
4       viewClass = JstlView.class;
5    }
6    setViewClass(viewClass);
7 }

  resolveViewName()方法在父类AbstractCachingViewResolver中实现,下面来看看视图是如何被解析出来的,AbstractCachingViewResolver#resolveViewName()核心伪代码

 1 public View resolveViewName(String viewName, Locale locale) throws Exception {
 2    // 如果禁用缓存,则创建 viewName 对应的 View 对象
 3    if (!isCache()) {
 4       return createView(viewName, locale);
 5    }
 6    else {
 7       // 获得缓存 KEY
 8       Object cacheKey = getCacheKey(viewName, locale);
 9       // 从 viewAccessCache 缓存中,获得 View 对象
10       View view = this.viewAccessCache.get(cacheKey);
11       // 如果获得不到缓存,则从 viewCreationCache 中,获得 View 对象
12       if (view == null) {
13          synchronized (this.viewCreationCache) {
14             // 从 viewCreationCache 中,获得 View 对象
15             view = this.viewCreationCache.get(cacheKey);
16             if (view == null) {
17                // Ask the subclass to create the View object.
18                // 创建 viewName 对应的 View 对象
19                view = createView(viewName, locale);
20                // 如果创建失败,但是 cacheUnresolved 为 true ,则设置为 UNRESOLVED_VIEW
21                if (view == null && this.cacheUnresolved) {
22                   view = UNRESOLVED_VIEW;
23                }
24                // 如果 view 非空,则添加到 viewAccessCache 缓存中
25                if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
26                   this.viewAccessCache.put(cacheKey, view);
27                   this.viewCreationCache.put(cacheKey, view);
28                }
29             }
30          }
31       }
32       
33       return (view != UNRESOLVED_VIEW ? view : null);
34    }
35 }
1、如果禁用缓存,创建视图
2、获取缓存的key

  获取缓存的Key,直接返回viewName,UrlBasedViewResolver#getCacheKey() 核心代码

1 // 获取缓存的key
2 protected Object getCacheKey(String viewName, Locale locale) {
3    // 重写了父类的方法,去除locale直接返回viewName
4    return viewName;
5 }
3、创建视图View  

  根据key从AbstractCachingViewResolver的viewAccessCache缓存中获取View视图,若为缓存中不存在,创建View视图并添加进缓存中;若缓存中存在,返回缓存中的视图。

  当前缓存中不存在需创建视图,创建视图 UrlBasedViewResolver#createView() 核心伪代码

1 // 创建视图
2 protected View createView(String viewName, Locale locale) throws Exception {
3    // ...
4    // 调用父类实现,加载视图
5    return super.createView(viewName, locale);
6 }

-> 创建视图 AbstractCachingViewResolver#createView() 核心代码

1 // 创建视图
2 protected View createView(String viewName, Locale locale) throws Exception {
3    // 根据视图名称加载视图
4    return loadView(viewName, locale); 
5 }

 -> 加载视图 UrlBasedViewResolver#loadView() 核心伪代码

1 // 加载视图
2 protected View loadView(String viewName, Locale locale) throws Exception {
3    // 创建 viewName 对应的 View 对象
4    AbstractUrlBasedView view = buildView(viewName);
5    // 完成view实例初始化bean的处理
6    View result = applyLifecycleMethods(viewName, view);
7    // 返回view对象
8    return (view.checkResource(locale) ? result : null);
9 }

构建并返回View视图,构建View视图,UrlBasedViewResolver#buildView() 核心伪代码:

 1 protected AbstractUrlBasedView buildView(String viewName) throws Exception {
 2    // 获取Class对象,在InternalResourceViewResolver类构造器中设置JstlView.class
 3    Class<?> viewClass = getViewClass();
 4    // 通过反射实例化View对象
 5    AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
 6    // 设置url: 配置文件中 前缀 + 视图名称 + 后缀
 7    view.setUrl(getPrefix() + viewName + getSuffix());
 8    view.setAttributesMap(getAttributesMap());
 9    // ...
10    return view;
11 }

  完成View对象的创建及视图路径url的拼接设置,返回view对象详情。

  

4、 如果视图创建失败,但是 cacheUnresolved 为 true ,则设置为 UNRESOLVED_VIEW 进缓存中
5、如果解析视图失败返回null,否则返回解析的View视图对象

2.2、ModelAndView属性view类型为View

  如果ModelAndView属性view为View类型,直接获取View对象,若属性为空,抛出异常。

2.3、View页面渲染

  页面渲染,AbstractView#render() 核心伪代码:

 1 public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
 2       HttpServletResponse response) throws Exception {
 3 
 4    // 合并返回结果,将 Model 中的静态数据和请求中的动态数据进行合并
 5    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
 6    // 进行一些准备工作(修复 IE 中存在的 BUG)
 7    prepareResponse(request, response);
 8    // 进行渲染
 9    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
10 }
1、将request请求中的参数与model属性中的参数合并
2、进行页面渲染

  页面渲染,InternalResourceView#renderMergedOutputModel() 核心伪代码

 1 protected void renderMergedOutputModel(
 2       Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
 3 
 4    // 将model内容设置进request的属性中
 5    exposeModelAsRequestAttributes(model, request);
 6 
 7    // 往请求中设置一些属性,Locale、TimeZone、LocalizationContext
 8    exposeHelpers(request);
 9 
10    // 获取需要转发的路径
11    String dispatcherPath = prepareForRendering(request, response);
12 
13    // 获取请求转发器
14    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
15    
16    // ...
17    // 进行转发
18    rd.forward(request, response);
19 }

  1、将model属性设置到request的attribute属性中,并设置request其他相关属性

  2、根据需要转发的路径与request请求,获取请求转发器

  3、由请求转发器进行转发,后续流程由tomcat处理。

 

 

标签:null,viewName,SpringMVC,视图,源码,ModelAndView,view,View
From: https://www.cnblogs.com/RunningSnails/p/17133432.html

相关文章

  • 【Spring IOC】【五】容器源码解析- 属性填充populateBean
    1 前言好了,我们这篇文章讲解下populateBean,也就是bean的属性填充,并不仅仅是设置值,还有很多事情要做的。比如你的属性值类型转换、表达式解析等,关于属性填充的一些知识,本......
  • linux源码解析13- 反向映射RAMP详解
    1.什么是反向映射是一种物理地址反向映射虚拟地址的方法;正向映射:用户访问的虚拟地址,经过多级页表转化,最终映射到物理页面;反向映射:根据物理页面,找到所有映射到这个页面的......
  • ZYNQ FSBL源码分析
    ​ FSBL是ZYNQ的bootloader虽然不是第一个启动的,但属于用户可以更改的启动程序,因此对源码分析是非常有必要的(在FSBL之前有bootRom,这个已经固化)zynq在运行完芯片内固......
  • 摄像头视频云台控制PTZ前端html css原生样式源码分享
        ​<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0">......
  • SpringMvc5整合Thymeleaf-纯注解
    SpringMvc5整合Thymeleaf-纯注解一、环境准备1、整体项目结构2、pom依赖<dependencies><!--springmvc相关依赖--><dependency><groupId>org.sprin......
  • 智慧校园:带微信小程序端源码
    智慧校园-基础数据管理1、学校信息:支持管理员对学校对基本学校信息进行编辑并浏览,通过编辑提交后全校可查看2、学科设置:支持管理添加并编辑以及删除学科,添加学科时系统自动......
  • Linux系列教程(十三)——Linux软件包管理之源码包、脚本安装包
    上篇博客我们讲解了网络yum源和光盘yum源的搭建步骤,然后详细介绍了相关的yum命令,yum最重要是解决了软件包依赖性问题。在安装软件时,我们使用yum命令将会简单方便很多。......
  • SpringMVC:RESTful
    目录RESTful简介①资源②资源的表述③状态转移RESTful的实现HiddenHttpMethodFilterRESTful简介REST:RepresentationalStateTransfer,表现层资源状态转移。①资源资源......
  • Semaphore源码解析
    Semaphore源码解析描述:一个计数信号量。从概念上讲,信号量维护一组许可。每个acquire()方法在必要时阻塞,直到获得许可,然后才能使用它。每次release()释放一个许可,潜在......
  • day12-SpringMVC文件上传
    SpringMVC文件上传1.基本介绍SpringMVC为文件上传提供了直接的支持,这种支持是通过即插即用的MultipartResolver实现的。spring用JacartaCommonsFileUpload技术......