DispatcherServlet的九大组件——ViewResolver组件——万字长文

1:为什么要学习DispatcherServlet的九大组件

在如下的这篇文章中
从一个请求入口来带你探究DispatcherServlet的奥秘——SpringMVC的核心组件——万字长文
已经探讨过了,为什么一个请求最终会到DispatcherServlet中的doDispatch方法中实现和该方法中的执行可以分为11个逻辑代码块。那为什么今天要聊这个话题呢?

这些组件对我们今后的开发是否有帮助呢?以我之愚见,我觉得是有的,举一个例子:九大组件之中的处理器异常解析器组件,如果我们去探究HandlerExceptionResolver 的源码,在以后的开发中我们就可以自定义实现 HandlerExceptionResolver 处理异常(可以作为默认的全局异常处理规则)等等。

那这个九大组件和doDispatch方法又有什么关系呢?在上面的文章中的末尾我们已经提到
doDispatch方法中出现了很多组件,这些组件都是Spring MVC整个处理过程中不可获缺的部分。正是通过这些组件之间的搭配组合,才令整个Spring MVC框架完整地运行起来,使用框架进行开发时,各种便捷功能都是通过这些组件辅助完成的。这就像盖房子一样,通过一个个小组件,最终搭建起一个完整的艺术品。

在接下来的三章中,会直接切入正题带大家了解视图解析器的查找过程和视图执行渲染的过程,然后走一遍完整的视图解析过程,最后带大家总结SpringMVC对视图的支持。
在这里插入图片描述
在这先强调,下面出现的resolveViewName方法是视图解析器实现类中的方法,我们通过打断点进行调试会发现,它出现在DispatcherServlet类中的doDispatch方法中的processDispatchResult方法中的render方法中的resolveViewName方法中,在DispatcherServlet类的resolveViewName方法中会进行视图解析(就是调用视图解析器实现类中的resolveViewName方法),如上过程要通过大家调试源码一步一步进行实现,在上面的文章4.5中有详细介绍。

这里就给大家展示一下DispatcherServlet类的resolveViewName方法的逻辑代码
在这里插入图片描述

3:视图解析器的查找过程

在大多数情况下,开发者返回的视图都是视图名的形式 ,视图解析器就是用来把视图名解析为视图数据的组件。其中仅包含一个方法,即View resolveViewName(String viewName,Locale locale):通过视图名与Locale区域信息解析为视图类型。

在DispatcherServlet中该组件为列表,使用时同样会遍历组件列表查找到第一 个返回不为空的View作为最终的View使用。在只添加Thymeleaf模板引擎依赖的情况下,该组件列表包含五个组件,按顺序如下。

  1. ContentNegotiatingViewResolver:整合全部视图解析器,附加内容类型协商逻辑。

  2. BeanNameViewResolver:根据视图名获取Bean,把Bean作为View返回。

  3. ThymeleafViewResolver:Thymeleaf 模板视图解析器。

  4. ViewResolverComposite:组合视图解析器类,用于组合多个视图解析器组件,内部维护视图解析器列表,执行解析时遍历内部视图解析器列表进行解析。内部视图解析器列表默认为空。

  5. InternalResourceView:内部资源视图解析器, 用于解析内部的些视图资源,如jsp等视图。
    上面的一段话的意思,如果放入到源码中就会很好理解。如下
    在这里插入图片描述

视图解析器返回值为View视图类型,其为视图的封装,主要作用是根据Model模型及请求与响应,对结果进行渲染, 把渲染后的结果写入响应体中,其提供的方法如下。

default Sring getConentType():返回视图的ContentType用于请求头 Acpetp做判断,以确定此视图是否支持Accpet中的任意一种MediaType。默认为null, 表示无显式声明类型。

void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServlet-Response response):根据Model模型对视图进行渲染,并把渲染内容写入响应体。
上面说到的视图会在第4章和第6.1节详细讲到,这里不在介绍

下面分别来了解一下ViewResolver组件中的五大视图解析器。

3.1 内容协商视图解析器

ContentNegotiatingViewResolver包含内容协商的逻辑,在通过视图名无法直接获取视图时,可根据请求头中声明的可接收内容类型,解析内容类型对应的文件扩展名,并自动为视图名添加文件扩展名再次解析。

ContentNegotiatingViewResolver类下的resolveViewName方法的逻辑代码如下:

@Override
	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws Exception {
   
		RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
		Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
//根据请求头Accept获取请求的MediaTypes列表	
		List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
//如果请求的MediaTypes不为空
		if (requestedMediaTypes != null) {
   
//根据视图名和请求MediaType获取全部视图列表		
			List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
//在有多个视图返回是,根据固定策略获取最佳匹配视图			
			View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
//不为空则返回			
			if (bestView != null) {
   
				return bestView;
			}
		}
//如果没有找到,则视图用于响应写入406状态码		
		String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
				" given " + requestedMediaTypes.toString() : "";

		if (this.useNotAcceptableStatusCode) {
   
			if (logger.isDebugEnabled()) {
   
				logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
			}
			return NOT_ACCEPTABLE_VIEW;
		}
//否则放回null,通过下一个视图解析器进行解析		
		else {
   
			logger.debug("View remains unresolved" + mediaTypeInfo);
			return null;
		}
	}

我们可以通过断点调试,在如下处打一个断点,然后debug启动,进入到该断点所在处的方法中执行。来演示上述执行过程
在这里插入图片描述
在这里插入图片描述
最终返回的View类型依赖于从候选列表里获取的视图类型。获取候选视图列表与选择最佳视图的方法的代码如下一一介绍:

获取候选视图列表代码逻辑如下

private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
			throws Exception {
   
//用于保存匹配的视图候选列表			
		List<View> candidateViews = new ArrayList<>();
		if (this.viewResolvers != null) {
   
			Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
//遍历全部视图解析器			
			for (ViewResolver viewResolver : this.viewResolvers) {
   
//尝试直接获取视图			
				View view = viewResolver.resolveViewName(viewName, locale);
//如果直接获取视图不为空,添加到候选列表				
				if (view != null) {
   
					candidateViews.add(view);
				}
//遍历请求中全部Accept头中的MediaType				
				for (MediaType requestedMediaType : requestedMediaTypes) {
   
// 通过内容协商器, 根据MediaType获取该MediaType对应的文件扩展名列表				
					List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
//遍历全部扩展名					
					for (String extension : extensions) {
   
						String viewNameWithExtension = viewName + '.' + extension;
//再次尝试获取视图						
						view = viewResolver.resolveViewName(viewNameWithExtension
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值