spring-freemarker.xml 视图解析器 ContentNegotiatingViewResolver 源码分析

视图解析器 ContentNegotiatingViewResolver

 

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

      http://www.springframework.org/schema/beans/spring-beans-4.0.xsd

      http://www.springframework.org/schema/tx

      http://www.springframework.org/schema/tx/spring-tx-4.0.xsd

      http://www.springframework.org/schema/aop 

      http://www.springframework.org/schema/aop/spring-aop-4.0.xsd

      http://www.springframework.org/schema/context

      http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <context:property-placeholder location="classpath:config.properties" ignore-unresolvable="true" />

    <bean id="freemarkerSettings" class="org.springframework.beans.factory.config.PropertiesFactoryBean">

        <property name="location" value="classpath:freemarker.properties" />

    </bean>

    <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">

        <property name="templateLoaderPath" value="/WEB-INF/views/default/" />

        <property name="freemarkerVariables">

            <map>

                <entry key="xml_escape" value-ref="fmXmlEscape" />

                <entry key="basePath" value="${BASE_PATH}" />

                <entry key="resPath" value="${BASE_RES_PATH}" />

                <entry key="common" value-ref="businessUtil" />

            </map>

        </property>

        <property name="freemarkerSettings" ref="freemarkerSettings" />

    </bean>

    <bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape" />

<bean id="cnManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">

        <property name="ignoreAcceptHeader" value="true"/>

        <property name="favorPathExtension" value="true"/>

        <property name="defaultContentType" value="text/html"/>

        <property name="favorParameter" value="true"/>

        <property name="mediaTypes">

            <map>

                <!-- 

                <entry key="xml" value="application/xml"/>

                <entry key="json" value="text/plain"/>

                <entry key="xls" value="application/vnd.ms-excel"/>

                 -->

                <entry key="do" value="text/html" />

                <entry key="json" value="application/json" />

            </map>

        </property>

    </bean>

    

    <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">

        <!-- spring 之前的版本配置。改成了ContentNegotiationManagerFactoryBean管理配置 -->

        <!--  

        <property name="ignoreAcceptHeader" value="true" />

        <property name="defaultContentType" value="text/html" />

        <property name="favorParameter" value="false" />

        <property name="mediaTypes">

            <map>

                <entry key="do" value="text/html" />

                <entry key="json" value="application/json" />

            </map>

        </property> -->

        <property name="contentNegotiationManager" ref="cnManager"/>  

        <property name="viewResolvers">

            <list>

                <bean class="com.spinach.support.spring.freemarker.BaseFreeMarkerViewResolver">

                    <property name="cache" value="false" />

                    <property name="contentType" value="text/html;charset=UTF-8;" />

                    <property name="prefix" value="" />

                    <property name="suffix" value=".ftl" />

                    <property name="exposeSpringMacroHelpers" value="true" />

                    <property name="requestContextAttribute" value="request" />

                </bean>

                <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">

                    <property name="cache" value="false" />

                    <!-- text/html; charset=utf-8 -->

                    <property name="contentType" value="application/json;charset=UTF-8;" />

                    <property name="prefix" value="" />

                    <property name="suffix" value=".json.ftl" />

                    <property name="exposeSpringMacroHelpers" value="true" />

                    <property name="requestContextAttribute" value="request" />

                </bean>

                <!-- jsp和freemarker同时存在,FreeMarkerViewResolver要在前面 -->

                <!-- <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  

                    <property name="prefix" value="/WEB-INF/view/jsp/"></property>配置页面路径  

                    <property name="suffix" value=".jsp"></property>文件以value值结尾  

                </bean> -->  

            </list>

        </property>

        <property name="defaultViews">

            <list>

                <!-- <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" /> -->

                <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">

                    <property name="prettyPrint" value="true"/>  

                    <property name="contentType" value="text/plain"/>  

                </bean>

                <!-- <bean class="org.springframework.web.servlet.view.xml.MarshallingView">  

                    <constructor-arg ref="jaxb2Marshaller">  

                    </constructor-arg>  

                </bean> -->  

                <!-- <bean class="com.wonders.stpt.bid.controller.JXLExcelView"/> -->

            </list>

        </property>

    </bean>

</beans>


 

 

步骤概要:

方法resolveViewName中根据方法getMediaTypes获取mediaType(由accept-header、请求参数或后缀名中获取,springmvc每个view的默认contenttype为text/html),根据访问的viewname与mediaType查找候选视图集合candidateViews,再根据方法getBestView获取最优视图并返回。

其中,ContentNegotiatingViewResolver 中包含2个属性:

ContentNegotiationManagerFactoryBean(XML中viewResolver):使用ContentNegotiationStrateg解析策略构造ContentNegotiationManager(afterPropertiesSet方法中初始化)

ContentNegotiationManager(XML中cnManager):包含List<ContentNegotiationStrategy> contentNegotiationStrategies属性记录匹配请求的解析策略,可能涉及的解析策略如下

1、org.springframework.web.accept.ServletPathExtensionContentNegotiationStrategy

后缀名,例:请求为 aaa.json

2、org.springframework.web.accept.ParameterContentNegotiationStrategy

参数,默认参数名称为format,例:请求为format=json

3、org.springframework.web.accept.HeaderContentNegotiationStrategy

通过accept-header匹配 content-type

4、org.springframework.web.accept.FixedContentNegotiationStrategy

通过设定defaultContentType content-type

 

favorPathExtension表示支持后缀匹配,favorParameter表示支持参数匹配,属性ignoreAcceptHeader默认为fasle,表示accept-header匹配,defaultContentType开启默认匹配。

 

其中cnManager中的mediaTypes预设key对应后缀名及参数format的值, 与匹配策略1、2相关。

例如:请求aaa.xx,若设置<entry key="xx" value="application/xml"/> 也能匹配以xml返回。

根据以上条件进行一一匹配最终,得到相关并符合的策略初始化ContentNegotiationManager


 

1、getMediaTypes方法使用ContentNegotiationManager的resolveMediaTypes方法(使用上述匹配策略)得到需要的contenttype类型例:请求aaa.xx,若设置<entry key="xx" value="application/xml"/> ,返回application/xml的MediaType

 

2、getCandidateViews方法根据XML中预设的viewResolvers匹配1中的MediaType获取候选View,同时增加XML中defaultViews中的view。每个view都有对应的content-type,大部分的视图继承于AbstractView,默认值都是text/html;charset=ISO-8859-1。有些特定视图,比如MappingJackson2JsonView会重设contentType为application/json。每个view中的contentType在最后匹配中都会实际用到,所以很重要。

在此方法中,会调用每一个viewResolvers的resolveViewName方法创建view,需要传入的参数为viewname与locale,创建过程中,

 

会先从cache中查看是否已经匹配过,若存在,直接返回view;若不存在,继续创建view。

第一步:检测viewResolvers中的viewNames属性,<property name="viewNames" value=".ftl"/>(支持正则表达式 如*.ftl,),

表达式:return (viewNames == null || PatternMatchUtils.simpleMatch(viewNames, viewName));

若未设置viewNames属性,则默认为null,允许创建,进入第二步;若设置,会根据viewNames进行正则表达式的匹配,若匹配继续第二步,若不匹配,返回null view,跳出此viewResolver;继续与下一个viewresolvers进行匹配并获得view。

第二步:进行每个viewResolve都有的view的创建过程(初始化content-type,记录最终跳转页面prefix,postfix等),但诸如Freemarker,velocity 的 viewResolvers都会在view创建过程中的最后一步调用重写checkResource方法,查看最终跳转的ftl或vm文件是否存在?若不存在,则将创建的视图View重设成null并返回,故无法匹配并加入候选视图。

而InternalResourceView的checkResource没有重写,调用父类AbstractUrlBasedView的checkResource,默认返回true,为所有请求创建view,无论是否有最终文件。所以,InternalResourceView一般都作为springmvc最后的视图解析器,来处理一切请求。

 

[java] view plain copy

  1. private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)  
  2.             throws Exception {  
  3.   
  4.         List<View> candidateViews = new ArrayList<View>();  
  5.         for (ViewResolver viewResolver : this.viewResolvers) {  
  6.             View view = viewResolver.resolveViewName(viewName, locale);  
  7.             if (view != null) {  
  8.                 candidateViews.add(view);  
  9.             }  
  10.             for (MediaType requestedMediaType : requestedMediaTypes) {  
  11.                 List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);  
  12.                 for (String extension : extensions) {  
  13.                     String viewNameWithExtension = viewName + "." + extension;  
  14.                     view = viewResolver.resolveViewName(viewNameWithExtension, locale);  
  15.                     if (view != null) {  
  16.                         candidateViews.add(view);  
  17.                     }  
  18.                 }  
  19.             }  
  20.         }  
  21.         if (!CollectionUtils.isEmpty(this.defaultViews)) {  
  22.             candidateViews.addAll(this.defaultViews);  
  23.         }  
  24.         return candidateViews;  
  25.     }  


 

 

3、getBestView方法获取最符合条件的匹配视图

首先,如果是smartView,即redirectView则return

否则:筛选getMediaTypes返回的content-type类型,与candidateViews中的候选视图匹配,其中:

下面的contentType重写了MappingJackson2JsonView的contentType,用于匹配cnManager中的mediaTypes,若该视图可以解析此contenttype,则返回

例子:若访问localhost/xxx/a.json,匹配策略为后缀匹配(favorExtension开启),由于后缀为json,则匹配cnManager中的text/plain筛选view后发现匹配视图为MappingJackson2JsonView,返回json格式的数据;若访问localhost/xxx/a,由于无后缀及参数匹配,则匹配策略为accept-header匹配,由于content-type为text/html,则匹配InternalResourceView中的视图,返回jsp页面。

你可能注意到,上述viewResolvers中配置了两个视图,为什么匹配InternalResourceView,而不是freeMarker?这是因为进入候选试图的只有InternalResourceView视图解析器,具体原因参见getCandidateViews。

 

 

[html] view plain copy

  1. <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">  
  2.                     <!-- <property name="extractValueFromSingleKeyModel" value="true"/> -->  
  3.                     <property name="prettyPrint" value="true"/>  
  4.                     <property name="contentType" value="text/plain"/>  
  5.                 </bean>  

 

 

[java] view plain copy

  1. private View getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes, RequestAttributes attrs) {  
  2.         for (View candidateView : candidateViews) {  
  3.             if (candidateView instanceof SmartView) {  
  4.                 SmartView smartView = (SmartView) candidateView;  
  5.                 if (smartView.isRedirectView()) {  
  6.                     if (logger.isDebugEnabled()) {  
  7.                         logger.debug("Returning redirect view [" + candidateView + "]");  
  8.                     }  
  9.                     return candidateView;  
  10.                 }  
  11.             }  
  12.         }  
  13.         for (MediaType mediaType : requestedMediaTypes) {  
  14.             for (View candidateView : candidateViews) {  
  15.                 if (StringUtils.hasText(candidateView.getContentType())) {  
  16.                     MediaType candidateContentType = MediaType.parseMediaType(candidateView.getContentType());  
  17.                     if (mediaType.isCompatibleWith(candidateContentType)) {  
  18.                         if (logger.isDebugEnabled()) {  
  19.                             logger.debug("Returning [" + candidateView + "] based on requested media type '"  
  20.                                     + mediaType + "'");  
  21.                         }  
  22.                         attrs.setAttribute(View.SELECTED_CONTENT_TYPE, mediaType, RequestAttributes.SCOPE_REQUEST);  
  23.                         return candidateView;  
  24.                     }  
  25.                 }  
  26.             }  
  27.         }  
  28.         return null;  
  29.     }  

转载于:https://my.oschina.net/spinachgit/blog/1023435

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值