文档章节

Spring 源码分析(四) ——MVC(七)视图呈现

水门-kay
 水门-kay
发布于 2016/04/02 19:25
字数 1214
阅读 794
收藏 6

DispatcherServlet 视图设计

        前面分析了 Spring MVC 中的 M(Model)和 C(Controller)相关的实现,其中的 M 大致对应 ModelAndView 的生成,而 C 大致对应 DispatcherServlet 和与用户业务逻辑相关的 handler 实现。在 Spring MVC 框架中,DispatcherServlet 起到了非常核心的作用,是整个 MVC 框架的调度枢纽。对应视图呈现功能,它的调用入口同样在 DispatcherServlet 中的 doDispatch 方法中实现。具体来说,它的调用入口是 DispatcherServlet 中的 render 方法。

DispatcherServlet.java 

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
   // 从 request 中读取 locale 信息,并设置 response 的 locale 值
   Locale locale = this.localeResolver.resolveLocale(request);
   response.setLocale(locale);

   View view;
   // 根据 ModleAndView 中设置的视图名称进行解析,得到对应的视图对象
   if (mv.isReference()) {
      // 需要对象视图名进行解析
      view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
      if (view == null) {
         throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
               "' in servlet with name '" + getServletName() + "'");
      }
   }
   // ModelAndView 中有可能已经直接包括了 View 对象,那就可以直接使用
   else {
      // 直接从 ModelAndView 对象中取得实际的视图对象
      view = mv.getView();
      if (view == null) {
         throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
               "View object in servlet with name '" + getServletName() + "'");
      }
   }

   // 提交视图对象进行展现
   if (logger.isDebugEnabled()) {
      logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
   }
   try {
      // 调用 View 实现对数据进行呈现,并通过 HTTPResponse 把视图呈现给 HTTP 客户端
      view.render(mv.getModelInternal(), request, response);
   }
   catch (Exception ex) {
      if (logger.isDebugEnabled()) {
         logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
               getServletName() + "'", ex);
      }
      throw ex;
   }
}

protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
      HttpServletRequest request) throws Exception {

   // 调用 ViewResolver 进行解析
   for (ViewResolver viewResolver : this.viewResolvers) {
      View view = viewResolver.resolveViewName(viewName, locale);
      if (view != null) {
         return view;
      }
   }
   return null;
}


View 接口的设计

        下面需要对得到的 View 对象,就行分析。 

        由此,我们可以看出,Spring MVC 对 常用的视图提供的支持。从这个体系中我们可以看出,Spring MVC 对常用视图的支持,比如 JSP/JSTL 视图、FreeMaker 视图等等。View 的设计其实是非常简单的,只需要实现 Render 接口。

public interface View {
   String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
   String PATH_VARIABLES = View.class.getName() + ".pathVariables";
   String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
   String getContentType();
   void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}


JSP 视图的实现

        使用 JSP 的页面作为 Web UI,是使用 Java 设计 Web 应用比较常见的选择之一,如果在 JSP 中使用 Jstl(JSP Standard Tag Library)来丰富 JSP 的功能,在 Spring MVC 中就需要使用 JstlView 来作为 View 对象,从而对数据进行视图呈现。而 JstlView 没有实现 render 的方法,而使用的 render 方法是它的基类 AbstractView 中实现的。下面是他的主要时序图:

AbstractView.java 

public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
   if (logger.isTraceEnabled()) {
      logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
         " and static attributes " + this.staticAttributes);
   }

   // 这里把所有的相关信息都收集到一个 Map 里
   Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
   prepareResponse(request, response);
   // 展现模型数据到视图的调用方法
   renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

        这个基类的 render 方法实现并不困难,而它主要是完成数据的准备工作,比如把所有的数据模型进行整合放到 mergedModel 对象中,而它是一个 HasMap。然后,调用 renderMergedOutputModel()方法。

protected void renderMergedOutputModel(
      Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

   // 判断需要将哪一个请求的处理器交给 RequestDispatcher
   exposeModelAsRequestAttributes(model, request);

   // 对数据进行处理
   exposeHelpers(request);

   // Determine the path for the request dispatcher.
   String dispatcherPath = prepareForRendering(request, response);

   // Obtain a RequestDispatcher for the target resource (typically a JSP).
   RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
   if (rd == null) {
      throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
            "]: Check that the corresponding file exists within your web application archive!");
   }

   // If already included or response already committed, perform include, else forward.
   if (useInclude(request, response)) {
      response.setContentType(getContentType());
      if (logger.isDebugEnabled()) {
         logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
      }
      rd.include(request, response);
   }

   else {
      // Note: The forwarded resource is supposed to determine the content type itself.
      // 转发请求到内部定义好的资源上,比如 JSP 页面,JSP 页面的展现由 Web 容器完成,
      // 在这种情况下,View 只是起到转发请求的作用
      if (logger.isDebugEnabled()) {
         logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
      }
      rd.forward(request, response);
   }
}

AbstractView.java

protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
   for (Map.Entry<String, Object> entry : model.entrySet()) {
      String modelName = entry.getKey();
      Object modelValue = entry.getValue();
      if (modelValue != null) {
         request.setAttribute(modelName, modelValue);
         if (logger.isDebugEnabled()) {
            logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
                  "] to request in view with name '" + getBeanName() + "'");
         }
      }
      else {
         request.removeAttribute(modelName);
         if (logger.isDebugEnabled()) {
            logger.debug("Removed model object '" + modelName +
                  "' from request in view with name '" + getBeanName() + "'");
         }
      }
   }
}

JstlView.java

protected void exposeHelpers(HttpServletRequest request) throws Exception {
   if (this.messageSource != null) {
      JstlUtils.exposeLocalizationContext(request, this.messageSource);
   }
   else {
      JstlUtils.exposeLocalizationContext(new RequestContext(request, getServletContext()));
   }
}

InternalResourceView.java

protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
      throws Exception {

   // 从 request 中获取 URL 路径
   String path = getUrl();
   if (this.preventDispatchLoop) {
      String uri = request.getRequestURI();
      if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
         throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
               "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
               "(Hint: This may be the result of an unspecified view, due to default view name generation.)");
      }
   }
   return path;
}

        得到 URL 路径后,使用 RequestDispatcher 把请求转发到这个资源上,就完成了 JSTL 的 JSP 页面展现。



——水门(2016年4月写于杭州)

© 著作权归作者所有

水门-kay
粉丝 460
博文 19
码字总数 59660
作品 0
杭州
后端工程师
私信 提问
Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密...

小致Daddy
2018/08/03
21.7K
1
深入学习SpringMVC以及学习总结

一、优点: 1.SpringMVC简化web程序开发; 2.SpringMVC效率很好(单例模式); 3.SpringMVC提供了大量扩展点,方便程序员自定义功能; 如果想学习Java工程化、高性能及分布式、深入浅出。微服...

编程SHA
01/22
28
0
springmvc源码解析合集

更多精彩源码解析文章请关注”天河聊架构“微信公众号。 springmvc源码解析之组件介绍 springmvc源码解析之配置加载SpringServletContainerInitializer springmvc源码解析之配置加载Context...

天河2018
03/27
209
0
Spring 源码分析(四) ——MVC(八)总结

Spring MVC 的总结 Spring 并不会强制应用对 Web 框架的选择,但对于 Web 应用开发而言,选择直接使用 Spring MVC 可以给应用开发带来许多便利。 而对于整个 Spring MVC 框架的运行过程,首先...

水门-kay
2016/04/05
1K
0
Java Web 面试中关于Spring MVC必问题,不看血亏!

前言 Spring MVC是Spring构建在Servlet API上的Web框架。目前大部分的Java Web 开发已经使用Spring MVC 来做。它提供了模型 - 视图 - 控制器架构,可用于开发灵活的Web应用程序。在本教程中,...

码农小胖哥
09/23
49
0

没有更多内容

加载失败,请刷新页面

加载更多

Error和Exception

1.Error类和Exception类都是继承Throwable类 2.Error(错误)是系统中的错误,程序员是不能改变的和处理的,是在程序编译时出现的错误,只能通过修改程序才能修正。一般是指与虚拟机相关的问...

大瑞清_liurq
10分钟前
1
0
8086汇编基础 start 程序入口标签的示例

    IDE : Masm for Windows 集成实验环境 2015     OS : Windows 10 x64 typesetting : Markdown    blog : my.oschina.net/zhichengjiu    gitee : gitee.com/zhichengjiu   ......

志成就
15分钟前
3
0
uni app 零基础小白到项目实战2

<template> <scroll-view v-for="(card, index) in list" :key="index"> <view v-for =(item, itemIndex) in card"> {{item.value}}</view> </scroll-view></template> GraceUi va......

达达前端小酒馆
16分钟前
3
0
http keep-alive 解释

本文转载于:专业的前端网站➜http keep-alive 解释 1、概念 keep-alive示例: keep-alive模式(又称持久连接、连接重用)时,keep-alive功能使客户端到服务器端的连接持续有效,当出现对服务...

前端老手
21分钟前
3
0
groovy爬虫实例——历史上的今天

最近做了一个历史上今天的爬虫程序,跟历史天气数据源一致,数据量比较小,几十秒就爬完了。中间遇到一些问题,一起分享出来供大家参考。本项目源码和相关数据已经上传到了github,有兴趣的朋...

八音弦
31分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部