zoukankan      html  css  js  c++  java
  • SpringMVC视图机制详解[附带源码分析]

    目录

    前言

    SpringMVC是目前主流的Web MVC框架之一。 

    如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html

    本文将分析SpringMVC的视图这部分内容,让读者了解SpringMVC视图的设计原理。

    重要接口和类介绍

    1. View接口

      视图基础接口,它的各种实现类是无状态的,因此是线程安全的。 该接口定义了两个方法: 

    2. AbstractView抽象类

      View接口的基础实现类。我们稍微介绍一下这个抽象类。

      首先看下这个类的属性:

      再看下抽象类中接口方法的实现:

      getContentType方法直接返回contentType属性即可。

       render方法:

    3. AbstractUrlBasedView抽象类

      继承自AbstractView抽象类,增加了1个类型为String的url参数。

    4. InternalResourceView类

      继承自AbstractUrlBasedView抽象类的类,表示JSP视图。

      我们看下这个类的renderMergedOutputModel方法(AbstractView抽象类定义的抽象方法,为View接口提供的render方法服务)。

    5. JstlView类

      JSTL视图,继承自InternalResourceView,该类大致上与InternalResourceView类一致。

    6. AbstractTemplateView抽象类

      继承自AbstractUrlBasedView抽象类,重写了renderMergedOutputModel方法,在该方法中会调用renderMergedTemplateModel方法,renderMergedTemplateModel方法为新定义的抽象方法。

      该抽象类有几个boolean属性exposeSessionAttributes,exposeRequestAttributes。 设置为true的话会将request和session中的键值和值丢入到renderMergedTemplateModel方法中的model这个Map参数中。

      这个类是某些模板引擎视图类的父类。 比如FreemarkerView,VelocityView。

    7. FreeMarkerView类

      继承自AbstractTemplateView抽象类。

      直接看renderMergedTemplateModel方法,renderMergedTemplateModel内部会调用doRender方法:

    8. RedirectView类

      继承自AbstractUrlBasedView,并实现SmartView接口。SmartView接口定义了1个boolean isRedirectView();方法。

      该视图的renderMergedOutputModel方法主要就是通过response.sendRedirect进行重定向。

      有关RedirectView方面的知识楼主另外写了1篇博客。http://www.cnblogs.com/fangjian0423/p/springMVC-redirectView-analysis.html

    9. ViewResolver接口

      视图解释器,用来解析视图View,与View接口配合使用。

      该接口只有1个方法,通过视图名称viewName和Locale对象得到View接口实现类:

      View resolveViewName(String viewName, Locale locale) throws Exception;

    10. AbstractCachingViewResolver抽象类

      带有缓存功能的ViewResolver接口基础实现抽象类,该类有个属性名为viewAccessCache的以 "viewName_locale" 为key, View接口为value的Map。

      该抽象类实现的resolveViewName方法内部会调用createView方法,方法内部会调用loadView抽象方法。

    11. UrlBasedViewResolver类

      继承自AbstractCachingViewResolver抽象类、并实现Ordered接口的类,是ViewResolver接口简单的实现类。

      该类复写了createView方法:

      父类(AbstractCachingViewResolver)的createView方法内部会调用loadView抽象方法,UrlBasedViewResolver实现了这个抽象方法:

      下面对UrlBasedViewResolver做1个test,配置如下:

    <bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
      <property name="prefix" value="/WEB-INF/view/"/>
      <property name="suffix" value=".jsp"/>
      <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/>
      <property name="viewNames">
        <array>  
          <value type="java.lang.String">*</value>  
        </array>    
      </property>  
      <property name="contentType" value="text/html;charset=utf-8"/>
      <property name="attributesMap">
        <map>
          <entry key="mytest" value="mytestvalue"/>
        </map>
      </property>
      <property name="attributes">
        <props>
          <prop key="test">testvalue</prop>
        </props>
      </property>
    </bean>
    

      我们看到:以InternalResourceView这个JSP视图作为视图;viewNames我们设置了*,这里的*代表全部视图名(这个viewNames属性不设置也可以,代表全部视图名都处理);http响应头部contentType信息:text/html;charset=utf-8;attributesMap和attributes传入的Map和Properties参数都会被丢入到staticAttributes属性中,这个staticAttributes会被设置成AbstractView的staticAttributes属性,也就是request域中的参数。

      我们看到request域中没有设置mytest和testvalue值。但是页面中会显示,因为我们配置了attributesMap和attributes参数。

      如果我们把viewNames中的"*"改成"index1"。那么就报错了,因为处理视图名的时候index匹配不上index1。

    12. InternalResourceViewResolver类

      继承自UrlBasedViewResolver,以InternalResourceView作为视图,若项目中存在“javax.servlet.jsp.jstl.core.Config”该类,那么会以JstlView作为视图。重写了buildView方法,主要就是为了给InternalResourceView视图设置属性。

    13. AbstractTemplateViewResolver类

      继承自UrlBasedViewResolver,重写了buildView方法,主要就是构造AbstractTemplateView以及为它设置相应的属性。

    14. FreeMarkerViewResolver类

      继承自AbstractTemplateViewResolver,将视图设置为FreeMarkerView。

    15. ModelAndView对象

      顾名思义,带有视图和Model属性的一个模型和视图类。

      值得注意的是,这个视图属性是一个Object类型的数据,可以直接是View接口的实现类或者视图名(字符串)。

    源码分析

    下面我们来分析SpringMVC处理视图的源码。

    SpringMVC在处理请求的时候,通过RequestMappingHandlerMapping得到HandlerExecutionChain,然后通过RequestMappingHandlerAdapter得到1个ModelAndView对象,之后通过processDispatchResult方法处理。

    processDispatchResult方法如下:

    如果配置的ViewResolver如下:

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
      <property name="prefix" value="/WEB-INF/view/"/>
      <property name="suffix" value=".jsp"/>
    </bean>
    

    那么就是使用InternalResourceViewResolver来解析视图。

    之前分析过,InternalResourceViewResolver重写了UrlBasedViewResolver的buildView方法。但是还是会调用UrlBasedViewResolver的buildView方法。

    最终得到InternalResourceView或JstlView视图。这两个视图的render方法本文介绍重要接口及类的时候已分析。

    PS:DispathcerServlet中的viewResolvers属性是个集合,如果存在多个ViewResolver对象,必定会有优先级的问题,这部分的内容请参考楼主的另一篇博客:

    http://www.cnblogs.com/fangjian0423/p/spring-Ordered-interface.html

    编码自定义的ViewResolver

    下面,我们就来编写自定义的ViewResolver。

    自定义的ViewResolver处理视图名的时候,遇到 "jsp:" 开头的会找jsp页面,遇到 "freemarker:" 开头的找freemarker页面。

    public class CustomViewResolver extends UrlBasedViewResolver {
    
      public static final String JSP_URL_PREFIX = "jsp:";
      public static final String FTL_URL_PREFIX = "freemarker:";
      
      private static final boolean jstlPresent = ClassUtils.isPresent(
                "javax.servlet.jsp.jstl.core.Config", CustomViewResolver.class.getClassLoader());
    
      private Boolean exposePathVariables = false;
    
      private boolean exposeRequestAttributes = false;
    
      private boolean allowRequestOverride = false;
    
      private boolean exposeSessionAttributes = false;
    
      private boolean allowSessionOverride = false;
    
      private boolean exposeSpringMacroHelpers = true;
    
      public CustomViewResolver() {
          this.setViewClass(FreeMarkerView.class);
      }  
      
      @Override
      protected AbstractUrlBasedView buildView(String viewName) throws Exception { 
        if(viewName.startsWith(FTL_URL_PREFIX)) {
            return buildFreemarkerView(viewName.substring(FTL_URL_PREFIX.length()));
        } else if(viewName.startsWith(JSP_URL_PREFIX)) {
            Class viewCls = jstlPresent ? JstlView.class : InternalResourceView.class;
            return buildView(viewCls, viewName.substring(JSP_URL_PREFIX.length()), getPrefix(), ".jsp");
        } else {
            //默认以freemarker处理
            return buildFreemarkerView(viewName);
        }
      }
      
      private AbstractUrlBasedView build(Class viewClass, String viewName, String prefix, String suffix) { 
        AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
        view.setUrl(prefix + viewName + suffix);
        String contentType = getContentType();
        if (contentType != null) {
            view.setContentType(contentType);
        }
        view.setRequestContextAttribute(getRequestContextAttribute());
        view.setAttributesMap(getAttributesMap());
        if (this.exposePathVariables != null) {
            view.setExposePathVariables(exposePathVariables);
        }
        return view;    
      }
      
      private AbstractUrlBasedView buildFreemarkerView(String viewName) throws Exception { 
        AbstractTemplateView view = (AbstractTemplateView) build(FreeMarkerView.class, viewName, "", getSuffix());
        view.setExposeRequestAttributes(this.exposeRequestAttributes);
        view.setAllowRequestOverride(this.allowRequestOverride);
        view.setExposeSessionAttributes(this.exposeSessionAttributes);
        view.setAllowSessionOverride(this.allowSessionOverride);
        view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers);
        return view;      
      }
      
      //get set方法省略
      
    }
    

    xml配置:

    <bean class="org.format.demo.support.viewResolver.CustomViewResolver">
      <property name="prefix" value="/WEB-INF/view/"/>
      <property name="suffix" value=".ftl"/>
      <property name="contentType" value="text/html;charset=utf-8"/>
      <property name="exposeRequestAttributes" value="true"/>
      <property name="exposeSessionAttributes" value="true"/>
      <property name="exposeSpringMacroHelpers" value="true"/>
      <property name="requestContextAttribute" value="request"/>
    </bean>
    
    <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
      <property name="templateLoaderPath" value="/WEB-INF/view/"/>
      <property name="defaultEncoding" value="utf-8"/>
      <property name="freemarkerSettings">
        <props>
          <prop key="template_update_delay">10</prop>
          <prop key="locale">zh_CN</prop>
          <prop key="datetime_format">yyyy-MM-dd</prop>
          <prop key="date_format">yyyy-MM-dd</prop>
          <prop key="number_format">#.##</prop>
        </props>
      </property>
    </bean>
    

    简单解释一下:CustomViewResolver解析视图名的时候,判断 "jsp:" 和 "freemarker:" 开头的名字,如果是 "jsp:" 开头的,如果有JSTL依赖,构造JSTLView视图,否则构造InternalResourceView视图。如果是 "freemarker:" 构造FreemarkerView。在构造视图之前分别会设置一些属性。

    xml配置:配置prefix是为了给jsp视图用的,freemarker视图不需要prefix,因为FreemarkerView内部会使用配置的FreeMarkerConfigurer,并用FreeMarkerConfigurer内部的templateLoaderPath属性作为前缀,配置的suffix是为了让FreemarkerView使用,当后缀。

    最后附上Controller代码:

    @Controller
    @RequestMapping(value = "/tvrc")
    public class TestViewResolverController {
      
        @RequestMapping("jsp")
        public ModelAndView jsp(ModelAndView view) {
            view.setViewName("jsp:trvc/index");
            return view;
        }
    
        @RequestMapping("/ftl")
        public ModelAndView freemarker(ModelAndView view) {
            view.setViewName("freemarker:trvc/index");
            return view;
        }
      
    }

    视图 /WEB-INF/view/trvc/index.jsp 中的的内容是输出<h2>This is jsp page</h2>

    视图 /WEB-INF/view/trvc/index.ftl 中的的内容是输出<h2>This is freemarker page</h2>

    总结

    本文分析了SpringMVC中的视图机制,View和ViewResolver这两个接口是视图机制的核心,并分析了几个重要的View和ViewResolver接口实现类,最终写了一个区别jsp和freemarker视图的ViewResolver实现类,让读者更加理解视图机制。

    希望这篇文章能帮助读者了解SpringMVC视图机制。

    文中难免有错误,希望读者能够指明出来。

    参考资料

    http://my.oschina.net/HeliosFly/blog/221392

  • 相关阅读:
    EasyUI左边树菜单和datagrid分页
    Linux上安装Redis教程
    TreeMap和TreeSet的区别与联系
    将Map<String, List<Map<String,Object>>>进行排序
    Linux系统安装JDK和Tomcat
    点击添加按钮,使用ajax动态添加一行和移除一行,并且序号重新排序和数据不重复操作判断
    23种设计模式汇总整理
    SSH架构BaseDao实现
    双击Table表格td变成text修改内容
    用户找回密码功能JS验证邮箱通过点击下一步隐藏邮箱输入框并修改下一步按钮的ID
  • 原文地址:https://www.cnblogs.com/fangjian0423/p/springMVC-view-viewResolver.html
Copyright © 2011-2022 走看看