ViewResolver组件原理解析

目录

一、ViewResolver体系介绍

1、ViewResolver组件是啥

2、ViewResolver体系介绍

二、BeanNameViewResolver分析

2.1、示范例子

 配置视图解析器 

Controller

HelloView(view对象)

结果

源码分析

三、ContentNegotiatingViewResolver分析

3.1、前言

3.2、示例

ContentNegotiatingViewResolver解析器配置

Controller类

请求url后缀形式

请求url参数形式

请求url 请求头形式

3.3、核心方法分析

1、resolveViewName()方法

2、getMediaTypes()方法

四、AbstractCachingViewResolver相关类分析

4.1、示例

视图解析器的xml配置

Controller方法

 请求结果

4.2、resolveViewName方法

五、UrlBasedViewResolver方法

5.1、createView()方法

5.2、buildView()方法

六、InternalResourceViewResolver类

七、FreeMarkerViewResolver类

八、总结

 

一、ViewResolver体系介绍


1、ViewResolver组件是啥


    在我们学习SpringMVC框架的时候最熟悉的其 “一Servlet三组件”
          DispatcherServlet:SpringMVC处理请求的入口类
          HandlerMapping(处理器映射器):用于根据请求获取(映射)到一个Handler对象(通常是我们真实场景中的Controller的使用@RequestingMapping注解修饰的方法)
          HandlerAdapter(处理器适配器):对获取到的Handler对象进行调用处理获取到ModelView对象。
          ViewResolver(视图解析器):对于处理器适配器获取到的对象进行解析获取对应的视图最终呈现给浏览器进行渲染(比如jsp页面,freeMarket模板语言)
在前面的博文中我们已经分析了SpringMVC的处理流程、HandlerMapping原理分析、HandlerAdapter原理分析,现在该说一说ViewResolver组件的相关逻辑了。
总结:
   ViewResolver组件是根据String类型的视图名和对应的Locale(语言环境 国际化相关)解析出View对象,而View对象则是用来将数据填充进来,并解析成对应的html(也可能是其他类型)类型的文件渲染给前台。View对象是根据不同的类型使用某些模块来渲染的,比如针对jsp类型使用JstlView进行渲染。


2、ViewResolver体系介绍


   ViewResolver只有一个接口方法resolveViewName

/根据名字在不同的地区语言环境中解析出View视图对象 
View resolveViewName(String viewName, Locale locale) throws Exception;


ViewResolver接口全览


ViewResolver接口结构概览

 总体的该就有四大类(StaticViewResolver 除外 用于单元测试)
1、BeanNameViewResolver: 根据名字获取对应的视图对象
2、ContentNegituatingViewResolver: 根据请求的MediaTypes(context-type)来获取最佳的View对象
3、AbstractCachingViewResolver: 可以缓存解析过的视图对象的基类,亮点是视图解析后的缓存,实现了相关固定化的功能,提供变化的抽象功能交由子类实现,我们常使用的jsp相关的视图InternalResourceViewResolver,以及freeMarket模板语言的FreeMarkerViewResolver解析器。
4、ViewResolverComposite: 是包含如上各个ViewResolver的组合类

接下来我们分别对其中的三种不同类型进行分别分析BeanNameViewResolver
、ContentNegituatingViewResolver、AbstractCachingViewResolver(ViewResolverComposite组合类不分析),重点是我们真是场景中最常用的AbstractCachingViewResolver

二、BeanNameViewResolver分析

2.1、示范例子

  •    配置视图解析器 

Xml配置对象eanNameViewResolver 视图解析器,同时配置对应的view

<!--视图解析 -->

<!--1、BeanNameViewResolver视图解析器 根据controller类的方法返回的String

 类型的viewName  作为bean的name从spring中获取 View对象调用其的render()方法 -->

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

</bean>


<!--配置view对象 view对象在后面贴出-->

 <bean id="helloView" class="com.xiu.config.HelloView"/>
  • Controller

@Controller

@RequestMapping(value = "/beanNameView")

public class BeanNameViewController {

    @RequestMapping(value = "hello", method = RequestMethod.GET)
    public String toHello(HttpServletRequest request){
        System.out.println("使用BeanNameViewResolver解析器 获取解析视图");
        //返回时String 类型的View 对象
        return "helloView";
    }
}
  • HelloView(view对象)

/**
* @Desc 自定义的View对象
*/
public class HelloView implements View {
    /**
     * 该视图的media类型
     * @return
     */
    @Override
    public String getContentType() {
        return "text/html";
    }

    @Override  //核心方法用于将数据在浏览器中呈现
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        response.getWriter().print("Welcome to hello View:"+new Date());
    }
}

结果

从上面的示范例子中我们可以大概了解BeanNameViewResolver组件的功能,在我们Controller中返回一个“helloView”,同时也实例化了一个HelloView对象,结果渲染就使用调用HelloView对象的render()方法渲染数据信息到浏览器。总的来说BeanNameViewResolver就是通过我们Controller返回的string 作为beanName去spring容器中获取View对象调用其render方法渲染数据。
 

源码分析

通过上面的例子,我们对BeanNameViewResolver有了直观的了解,下面我们从源码的角度分析一下该类。

/**
     * 根据viewName解析视图对象
     */
    public View resolveViewName(String viewName, Locale locale) throws BeansException {
        //获取spring容器上下文环境
        ApplicationContext context = obtainApplicationContext();
        //容器中没有viewName对应的bean实例 则返回视图对象为null
        if (!context.containsBean(viewName)) {
            // Allow for ViewResolver chaining...
            return null;
        }
        //对于viewName不是View对象 则抛出异常
        if (!context.isTypeMatch(viewName, View.class)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Found bean named '" + viewName + "' but it does not implement View");
            }
            return null;
        }
        //根据viewName和View类型从spring容器中获取View的Bean实例
        return context.getBean(viewName, View.class);
    }

通过对该类分析,这个类很简单明白了其就是根据返回的ViewName从spring环境中获取View对象返回。

三、ContentNegotiatingViewResolver分析

3.1、前言

最近比较火的Restful风格接口指的是通过同一个url、不同的请求方式、请求头信息,可以针对同一个资源有不同的表述。比如

  1. GET /book/id获取某本书籍信息
  2. PUT /book/id更新某本书籍信息
  3. DELETE /book/id删除某本书籍

 

其实ContentNegotiatingViewResolver也可以很好的实现restful风格的接口同样的内容数据来使用不同的view来实现,.譬如需要实现如下功能,相同数据l,通过请求头,参数,后缀名等形式展示位不同的表现形式.

  • 使用url后缀的形式

http://xxx.com/book/{bookId}.xml    呈现xml文件

http://xxx.com//book/{bookId}.json    呈现json格式

  • 使用请求参数的形式

 

GET /user HTTP/1.1  Accept:application/xml

      GET /user HTTP/1.1  Accept:application/json

 

  • 使用请求头的形式

http://xxx.com/book/{bookId}?format=xml

http://xxx.combook/{bookId}?format=json

 

3.2、示例

ContentNegotiatingViewResolver解析器配置

	<!--2、ContentNegotiatingViewResolver视图解析器
	    根据http请求头中的context_type(media)来获取最符合的视图进行数据渲染
	    该解析器可以将相同的数据以不同的形式展示出来(xml,json,jsp)
	    三种方式
	    1、使用扩展名
	    http://www.test.com/user.xml    呈现xml文件
        http://www.test.com/user.json    呈现json格式
        http://www.test.com/user       使用默认view呈现,比如jsp等
        2、使用http request header的Accept
        GET /user HTTP/1.1
        Accept:application/xml
        GET /user HTTP/1.1
        Accept:application/json
        3、使用请求参数区分
        http://www.test.com/user?format=xml
        http://www.test.com/user?format=json
	 -->

	<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver" >
		<property name="order" value="0" />
		<!--spring5.0以后 使用contentNegotiationManagerFactoryBean 进行详细的配置-->
 		<property name="contentNegotiationManager"  ref="contentNegotiationManagerFactoryBean"/>
        <!-- 设置默认的视图对象,ContentNegotiatingViewResolver 根据类型匹配view的时候有两种形式
           1、使用视图解析器viewResolvers 来获取对用的view对象
           2、defaultViews 列出所有的view对象
        -->
		<property name="defaultViews">
			<list>
				<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"></bean>
				<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
					<constructor-arg>
						<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
							<property name="marshallerProperties">
								<map>
									<entry key="jaxb.encoding" value="UTF-8"></entry>
									<!-- 放置xml自动缩进属性 -->
									<entry key="jaxb.formatted.output" value="true"></entry>
								</map>
							</property>
							<property name="packagesToScan" value="com.xiu.entity"/>
						</bean>
					</constructor-arg>
				</bean>
			</list>
		</property>
	</bean>

	<!--具体启用上面的哪些方式由该bean来决定 -->
	<bean id="contentNegotiationManagerFactoryBean" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean" >
		<!--是否启用参数支持 默认为true 表明可以使用方式三-->
		<property name="favorParameter" value="true" />
		<!--是否忽略 request的header的accept true忽略 无法使用方式二-->
		<property name="ignoreAcceptHeader" value="false" />

		<property name="mediaTypes">
			<map>
                <entry key="html" value="text/html" />
				<entry key="json" value="application/json" />
				<entry key="xml" value="application/xml"/>                     </map>
		</property>

	</bean>

Controller类


@Controller
@RequestMapping(value = "/contentNegotiatingView")
public class ContentNegotiatingViewController {

    @RequestMapping(value="/book/{id}"
    public Book queryUser(@PathVariable("id") long id, ModelMap model) {
        Book book = new Book();
        book.setAuthor("陈忠实");
        book.setId(1111);
        book.setName("白鹿原");
        book.setPublisHouse("中信出版社");
        return book;
    }
}

 

请求url后缀形式

  • Xml形式

  • Json形式

 

 

请求url参数形式

  • Json形式

 

  • Xml形式

 

请求url 请求头形式

  • Accept:application/xml

  • Accept:application/json

3.3、核心方法分析

1、resolveViewName()方法

public View resolveViewName(String viewName, Locale locale) throws Exception {
        //从请求上下文中获取ServletRequestAttributes对象(封装过的ThreadLocal,其中保存了每次请求的HttpServletRequest对象)
        //只有是该HttpServletRequest 才能从其中获取到对应的mediaType类型
        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
        Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
        //根据HttpServletRequest解析其中的对应的media_type 和我们在xml中的配置有关
        // 比如配置favorParameter 为true则才可以使用xxxx?format=xml的形式 下面单独讲解
        //这一步获取到了请求的mediaType(可使用的)
        List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
        if (requestedMediaTypes != null) {
            //获取符合如上mediaType的所有视图view对象 获取视图对象可以从spring注册的ViewResolver 组件解析
            //也可以使用defaultViews配置的默认视图对象(先使用ViewResolver在使用defaultViews)
            List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
            //根据条件获取到最佳匹配的View对象返回
            View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
            if (bestView != null) {
                return bestView;
            }
        }
        //其他条件的日志处理省略
        ...
    }

该方法解析视图核心分成三大部分

  1. getMediaTypes方法: 从请求中根据我们配置调用其中不同的策略来解析请求可以接受的mediaType,主要起作用的是ContentNegotiationManager(这个类中的一些参数也是我们在xml配置的,我们用的不是该类本身而是其对应的FactoryBean对象)和ContentNegotiationStrategy类(按照url后缀、参数、请求头等策略)
  2. getCandidateViews方法:先交由注册在spring中的ViewResolver去解析对应的view,如果没有在从默认的views对象defauleViews集合中获取视图
  3. 上面解析出了meidaType集合和view对象集合 且按照优先级进行了排序,后面就使用meidaType和view解析的对应mediaType比对 匹配上直接返回该View对象,

 

上面的步骤中2\3步骤比较简单这里简单说明,我们下面主要讲解根据请求解析其中的MeidaType类型过程。

2、getMediaTypes()方法

protected List<MediaType> getMediaTypes(HttpServletRequest request) {
        Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
        try {
            ServletWebRequest webRequest = new ServletWebRequest(request);
            //解析请求可以接受的mediaType 
            List<MediaType> acceptableMediaTypes = this.contentNegotiationManager.resolveMediaTypes(webRequest);
            //获取请求本身的mediaType
            List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request);
            //上述解析出来的mediaType 进行兼容处理
            Set<MediaType> compatibleMediaTypes = new LinkedHashSet<>();
            for (MediaType acceptable : acceptableMediaTypes) {
                for (MediaType producible : producibleMediaTypes) {
                    if (acceptable.isCompatibleWith(producible)) {
                        compatibleMediaTypes.add(getMostSpecificMediaType(acceptable, producible));
                    }
                }
            }
            //获取到的最终mediaType按照优先级排序
            List<MediaType> selectedMediaTypes = new ArrayList<>(compatibleMediaTypes);
            MediaType.sortBySpecificityAndQuality(selectedMediaTypes);
            return selectedMediaTypes;
        }
        catch (HttpMediaTypeNotAcceptableException ex) {
            if (logger.isDebugEnabled()) {
                logger.debug(ex.getMessage());
            }
            return null;
        }
    }

该方法看着很多其实就两个核心 一个根据请求解析出其可接受mediaType,另一个获取请求本身带有的meidaType属性信息,两者兼容并按照优先级排序,其中比较考究的是解析request获取可接受的mediaType 这里是也是我们进行支持使用哪种方法(url后缀,请求参数还是请求头)配置的关键用到了ContentNegotiationStrategy,

public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
   //在该方法中ContentNegotiationStrategy代表不同的解析策略
  //如果我们在上面示例中的xml配置中设置
//favorParameter为true 对应ParameterContentNegotiationStrategy 按照xxxx?fromt=xxx的形式解析
 //ignoreAcceptHeader 为false 则对相应HeaderContentNegotiationStrategy请求头的形式解析
//ServletPathExtensionContentNegotiationStrategy对应请求路径后缀形式解析xxx.json
		for (ContentNegotiationStrategy strategy : this.strategies) {
			List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
			if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
				continue;
			}
			return mediaTypes;
		}
		return MEDIA_TYPE_ALL_LIST;
	}

四、AbstractCachingViewResolver相关类分析

  下面开始分析我们最常使用的视图解析器AbstractCachingViewResolver,这里以jsp页面和Freemarker页面为例子介绍AbstractCachingViewResolver 以及其相关获取视图对象的过程。

4.1、示例

  • 视图解析器的xml配置

<!-- 针对freemarker的视图配置 -->
	<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
		<property name="cache" value="true" />
		<property name="prefix" value="" />
		<property name="suffix" value=".ftl" />
		<property name="contentType" value="text/html;charset=UTF-8"></property>
		<property name="requestContextAttribute" value="request" />
		<property name="exposeSpringMacroHelpers" value="true" />
		<property name="exposeRequestAttributes" value="true" />
		<property name="exposeSessionAttributes" value="true" />
	</bean>

	<!-- freeMarkerViewResolver的配置信息设置 没有该配置spring无法实例化 -->
	<bean id="freemarkerConfig"
		  class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
		<property name="templateLoaderPath" value="WEB-INF/ftl/" />
		<property name="freemarkerSettings">
			<props>
				<prop key="template_update_delay">0</prop>
				<prop key="default_encoding">UTF-8</prop>
				<prop key="number_format">0.##########</prop>
				<prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
				<prop key="classic_compatible">true</prop>
				<prop key="template_exception_handler">ignore</prop>
			</props>
		</property>
	</bean>

	<!--配置jsp相关的视图解析器-->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    	<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
	    <property name="prefix" value="/WEB-INF/jsp/" />
	    <property name="suffix" value=".jsp" />
	</bean>

 

  • Controller方法

@Controller
@RequestMapping(value = "/internalResourceView")
public class InternalResourceViewController {
    /**
     * 使用InternaleResourceViewResolver进行jsp页面的渲染
     * @return 进入jsp页面
     */
    @RequestMapping(value="/jsp")
    public ModelAndView jspPage() {
       ModelAndView mv = new ModelAndView();
       mv.addObject("message","welcome jsp page");
       mv.setViewName("jspIndex");
       return mv;
    }
    /**
     * 使用FreeMarkerViewResolver进行freeMarker页面的渲染
     * @return 进入freemarker页面
     */
    @RequestMapping(value="/freeMarker")
    public ModelAndView freeMarkerPage() {
        ModelAndView mv = new ModelAndView();
        mv.addObject("message","welcome freeMarker page");
        mv.setViewName("freeMarkerIndex");
        return mv;
    }
}

 

 请求结果

     访问http://xxx/internalResourceView/freeMarker

 进入freemarker页面

 

 访问http://xxx/internalResourceView/jsp

进入jsp页面

4.2、resolveViewName方法

AbstractCachingViewResolver与上面两个相比,其增加了将解析出来的View对象进行缓存的功能(是否启用缓存是可配置的)下面看看其resolveViewName方法

public View resolveViewName(String viewName, Locale locale) throws Exception {
        //判断是否对解析出来的View对象(视图对象) 进行缓存
        //判断逻辑是其cacheLimit(允许缓存的视图对象个数)默认是1024 默认是走缓存 可以更改为不走缓存
        if (!isCache()) {
            //真是创建View的方法
            return createView(viewName, locale);
        }
        else {
            //先尝试从缓存中获取 该类使用两个Map作为缓存 viewAccessCache(ConcurrentHashMap)、viewCreationCache(linkedHashMap)
            Object cacheKey = getCacheKey(viewName, locale);
            View view = this.viewAccessCache.get(cacheKey);
            if (view == null) {
                synchronized (this.viewCreationCache) {
                    view = this.viewCreationCache.get(cacheKey);
                    if (view == null) {
                        // 缓存没有则进行创建工作
                        view = createView(viewName, locale);
                        if (view == null && this.cacheUnresolved) {
                            view = UNRESOLVED_VIEW;
                        }
                        //创建出来的View对象放入缓存中 并返回View对象
                        //下次获取直接从缓存中获取
                        if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
                            this.viewAccessCache.put(cacheKey, view);
                            this.viewCreationCache.put(cacheKey, view);
                        }
                    }
                }
            }
            else {
                if (logger.isTraceEnabled()) {
                    logger.trace(formatKey(cacheKey) + "served from cache");
                }
            }
            return (view != UNRESOLVED_VIEW ? view : null);
        }
    }

该方法很简单,先判断是否走缓存,不走缓存直接创建View对象,否则走缓存,先从缓存中获取,该类使用了两个Map作为缓存,第一个map是支持并发获取的ConcurrentHashMap,

第二个map是能提供对于缓存溢出时候的清楚工作(LinkedHashMap的removeEldestEntry方法),缓存中没有则执行创建View对象并放入缓存操作,真正创建View对象的方法为 createView()(该方法并非暴露给子类去实现,而是调用其内部的loadView()方法 该方法作为抽象方法交由子类重写)。其子类有三个XmlViewResolver、ResourceBundleViewResolver、

UrlBasedViewResolver、前两个的loadView()方法一致,XmlViewResolver使用xml形式配置View,ResourceBundleViewResolver使用Properties属性的形式配置View,最终从Spring容器中以viewName作为beanName获取对应的View实例对象,UrlBasedViewResolver我们在下面进行详细分析。

五、UrlBasedViewResolver方法

该类重写了子类的getCacheKey()、createView()、loadView()方法。

getCacheKey()取消了对locale(国际化)的支持。

5.1、createView()方法

具体创建视图的方法 对于转发、重定向、普通请求进行处理

 protected View createView(String viewName, Locale locale) throws Exception {
        //检查该视图解析器是否可以解析该viewName对应的视图
// 我们可以对viewName进行配置
        //从而进行检查的控制
        if (!canHandle(viewName, locale)) {
            return null;
        }

        //对于重定向请求的处理
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
            //获取重定向的url
            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            //创建对应的View对象
            RedirectView view = new RedirectView(redirectUrl,
                    isRedirectContextRelative(), isRedirectHttp10Compatible());
            String[] hosts = getRedirectHosts();
            if (hosts != null) {
                view.setHosts(hosts);
            }
            //对实例化后的View进行属性初始化 调用BeanFactory的initializeBean()方法
            return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
        }

        //对于转发请求的处理
        if (viewName.startsWith(FORWARD_URL_PREFIX)) {
            //获取重定向的url
            String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
            //创建对应的View对象
            InternalResourceView view = new InternalResourceView(forwardUrl);
            //对实例化后的View进行属性初始化 调用BeanFactory的initializeBean()方法
            return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
        }

        // 其他情况 普通请求 调用父类createView 最终调用子类的loadView()方法
        //loadView的处理逻辑和其上转发、重定向逻辑相同 先实例化View对象在初始化View对象
        //只是其实例化逻辑交由buildView()方法处理
        return super.createView(viewName, locale);
    }

在createView()方法中主要是针对转发、重定向、普通请求进行View的实例化和初始化

其初始化逻辑相同都是调用其BeanFactory的initializeBean()方法填充属性,此处不进行分析,相对于对象创建完成的属性设置,我们应该更关注View对象的创建,转发重定向都是直接new创建,普通请求需要调用buildView()方法来实现View对象的创建。

5.2、buildView()方法

protected AbstractUrlBasedView buildView(String viewName) throws Exception {
        //获取View对应的class (UrlBasedViewResolver子类中每一个类型都有只处理一个View对象)
        //比如FreeMarkerViewResolver 的viewClass 为FreeMarkerView
        //InternalResourceViewResolver的viewClass为JstlView
        //获取view class 并进行实例化
        Class<?> viewClass = getViewClass();
        Assert.state(viewClass != null, "No view class");
        AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
        //设置view的url
        view.setUrl(getPrefix() + viewName + getSuffix());
        view.setAttributesMap(getAttributesMap());
        //设置view的contentType
        String contentType = getContentType();
        if (contentType != null) {
            view.setContentType(contentType);
        }
        //设置view的requestContextAttribute 用于持有request对象的beanName
        String requestContextAttribute = getRequestContextAttribute();
        if (requestContextAttribute != null) {
            view.setRequestContextAttribute(requestContextAttribute);
        }
        //是否支持view使用PathVariables(url中的参数)
        Boolean exposePathVariables = getExposePathVariables();
        if (exposePathVariables != null) {
            view.setExposePathVariables(exposePathVariables);
        }
        //是否支持view可以使用spring容器中的bean实例
        Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
        if (exposeContextBeansAsAttributes != null) {
            view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
        }
        //是否配置view可以使用spring容器中的那些bean实例
        String[] exposedContextBeanNames = getExposedContextBeanNames();
        if (exposedContextBeanNames != null) {
            view.setExposedContextBeanNames(exposedContextBeanNames);
        }
        return view;
    }

 

以为这个类是一个王者,没想到逻辑简单到菜:获取viewClass通过反射形式进行实例化,并设置url、contextType、requestContextAttribute 等属性,顺便提一点的是getViewClass()方法获取viewClass,其UrlBasedViewResolver子类遵循每一个实例都对应一个viewClass其通过requiredViewClass来保证必须符合,则子类只需要setView()就可以完成相关功能。

 

六、InternalResourceViewResolver类

   InternalResourceViewResolver实现相关setViewClass()方法,同时重写了buildView()方法

该方法支持了forward的jsp页面include功能和防止循环引用

protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		InternalResourceView view = (InternalResourceView) super.buildView(viewName);
		if (this.alwaysInclude != null) {
//支持了forward的jsp页面include功能
			view.setAlwaysInclude(this.alwaysInclude);
		}
//防止循环引用
		view.setPreventDispatchLoop(true);
		return view;
	}

七、FreeMarkerViewResolver

FreeMarkerViewResolver类继承了AbstractTemplateViewResolver,该类重写了buildView()

在调用父类buildView()后为view对象填充了相关属性,属性的作用在代码中做了注释说明。

protected AbstractUrlBasedView buildView(String viewName) throws Exception {
		AbstractTemplateView view = (AbstractTemplateView) super.buildView(viewName);
        //是否允许所有的RequestAttributes(请求参数属性)被view对象使用
		view.setExposeRequestAttributes(this.exposeRequestAttributes);
        //对于存在RequestAttributes同名属性是否覆盖
		view.setAllowRequestOverride(this.allowRequestOverride);
//是否允许所有的SessionAttributes(session范围中的属性)被view对象使用
		view.setExposeSessionAttributes(this.exposeSessionAttributes);
        //对于存在SessionAttributes同名属性是否覆盖
		view.setAllowSessionOverride(this.allowSessionOverride);
        //不懂
		view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers);
		return view;
	}

八、总结

 到此有关ViewResolver的视图解析器组件相关源码分析已经结束。本篇博文主要针对ViewResolver接口三种类型的子类BeanNameViewResolver、ContentNegotiatingViewResolver、AbstractCachingViewResolver的resolveViewName的核心方法进行分析,同时附赠例子的github地址。每个子类都通过示例和源码分析并行的方式进行分析,核心还是介绍了我们真实开发场景中最常用的AbstractCachingViewResolver类以及其针对jsp视图渲染的InternalResourceViewResolver类和FreeMaker视图的FreeMarkerViewResolver类。

 

Github地址 :https://github.com/liushangzaibeijing/spsm.git

分支:dev_viewResolver

 

希望看完这边文章能对你有所帮助。

感谢你这么有才还来看我的文章。

 

 

 


版权声明:本文为liushangzaibeijing原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。