Spring MVC 源码分析之 DispatcherServlet#processDispatchResult方法

前言:

前面的篇章我们分析了 Spring MVC 工作流程中的 HandlerMapping、HandlerAdapter 的适配过程、拦截器的工作流程,以及处理业务请求的过程,本篇我们分析一下处理完业务解析视图的方法,也就是 DispatcherServlet#processDispatchResult 方法。

Spring MVC 知识传送门:

详解 Spring MVC(Spring MVC 简介)

Spring MVC 初始化源码分析

Spring MVC 工作流程源码分析

Spring MVC 源码分析之 DispatcherServlet#getHandler 方法

Spring MVC 源码分析之 DispatcherServlet#getHandlerAdapter 方法

Spring MVC 源码分析之 AbstractHandlerMethodAdapter#handle 方法

DispatcherServlet#processDispatchResult 方法源码分析

DispatcherServlet#processDispatchResult 方法名直译就是初始调度结果,其实就是解析 ModelAndView,源码也很清晰,先是判断是否有异常,有异常就解析异常视图,否则就开始解析 ModelAndView 的流程。

//org.springframework.web.servlet.DispatcherServlet#processDispatchResult
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
	boolean errorView = false;
	//是否有异常
	if (exception != null) {
		//异常是否是模型视图异常
		if (exception instanceof ModelAndViewDefiningException) {
			this.logger.debug("ModelAndViewDefiningException encountered", exception);
			//获取异常视图
			mv = ((ModelAndViewDefiningException)exception).getModelAndView();
		} else {
			//获取异常解析器
			Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
			//异常视图解析
			mv = this.processHandlerException(request, response, handler, exception);
			//异常视图不为空 赋值 errorView 为 true
			errorView = mv != null;
		}
	}
	//模型视图是否为空 模型视图是否被标识为清空
	if (mv != null && !mv.wasCleared()) {
		//解析并渲染视图 重点关注
		this.render(mv, request, response);
		//errorView 
		if (errorView) {
			//清除错误请求属性
			WebUtils.clearErrorRequestAttributes(request);
		}
	} else if (this.logger.isTraceEnabled()) {
		this.logger.trace("No view rendering, null ModelAndView returned.");
	}
	
	//判断是否是异步处理 
	if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
		//不是异步处理 
		if (mappedHandler != null) {
			//注册后置拦截器
			mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
		}

	}
}

DispatcherServlet#processHandlerException 方法源码分析

DispatcherServlet#processHandlerException 方法的作用就是解析异常视图,获取所有异常解析器,遍历解析视图,解析到视图就停止循环,设置视图相关属性返回。

//org.springframework.web.servlet.DispatcherServlet#processHandlerException
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
	//删除请求中的 HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性
	request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
	ModelAndView exMv = null;
	//异常解析器为空判断
	if (this.handlerExceptionResolvers != null) {
		//迭代遍历
		Iterator var6 = this.handlerExceptionResolvers.iterator();

		while(var6.hasNext()) {
			HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();
			//解析 
			exMv = resolver.resolveException(request, response, handler, ex);
			//解析到视图 就跳出循环
			if (exMv != null) {
				break;
			}
		}
	}
	//视图为 null 判断
	if (exMv != null) {
		//视图不为空
		if (exMv.isEmpty()) {
			//设置异常属性
			request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
			//返回
			return null;
		} else {
			//为空  判断是否有视图
			if (!exMv.hasView()) {
				//没有视图  获取默认视图名称
				String defaultViewName = this.getDefaultViewName(request);
				//默认视图名称为空判断
				if (defaultViewName != null) {
					//设置视图名称
					exMv.setViewName(defaultViewName);
				}
			}

			if (this.logger.isTraceEnabled()) {
				this.logger.trace("Using resolved error view: " + exMv, ex);
			} else if (this.logger.isDebugEnabled()) {
				this.logger.debug("Using resolved error view: " + exMv);
			}
			//设置请求相关的属性
			WebUtils.exposeErrorRequestAttributes(request, ex, this.getServletName());
			//返回视图
			return exMv;
		}
	} else {
		//视图为空抛出异常
		throw ex;
	}
}

DispatcherServlet#render 方法源码分析

DispatcherServlet#render 方法主要可以分为两步,分别是创建视图 View 和 解析渲染视图 view.render。

//org.springframework.web.servlet.DispatcherServlet#render
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
	//确认语言环境 常说的国际化
	Locale locale = this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale();
	//为响应设置 Locale
	response.setLocale(locale);
	//获取视图名称
	String viewName = mv.getViewName();
	View view;
	//视图名称为 null 判断
	if (viewName != null) {
		//解析视图名称 重点关注
		view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
		//视图为空判断
		if (view == null) {
			//视图为空 抛出异常
			throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'");
		}
	} else {
		//视图名称为 null
		//从模型视图中获取 视图
		view = mv.getView();
		//视图为 null 判断  
		if (view == null) {
			//视图为 null  抛出异常
			throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'");
		}
	}

	if (this.logger.isTraceEnabled()) {
		this.logger.trace("Rendering view [" + view + "] ");
	}

	try {
		//模型视图的状态判断
		if (mv.getStatus() != null) {
			//不为空 把模型视图的状态设置给 response
			response.setStatus(mv.getStatus().value());
		}
		//视图解析渲染 重点关注
		view.render(mv.getModelInternal(), request, response);
	} catch (Exception var8) {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Error rendering view [" + view + "]", var8);
		}

		throw var8;
	}
}

DispatcherServlet#resolveViewName 方法源码分析

DispatcherServlet#resolveViewName 方法的主要作用是通过遍历视图解析器获取到视图,视图获取成功则停止遍历,返回视图。

//org.springframework.web.servlet.DispatcherServlet#resolveViewName
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
	//视图解析器为空判断 这里的视图解析器 就是 DispatcherServlet.properties 中的视图解析器  InternalResourceViewResolver
	if (this.viewResolvers != null) {
		//迭代遍历视图解析器
		Iterator var5 = this.viewResolvers.iterator();

		while(var5.hasNext()) {
			//获取视图解析器
			ViewResolver viewResolver = (ViewResolver)var5.next();
			//通过视图名称和语言环境 得到视图 重点关注
			View view = viewResolver.resolveViewName(viewName, locale);
			//为null判断
			if (view != null) {
				//返回视图
				return view;
			}
		}
	}

	return null;
}

AbstractCachingViewResolver#resolveViewName 方法源码分析

AbstractCachingViewResolver#resolveViewName 方法主要就是创建视图,它会先判断是否允许使用缓存,然后去创建视图或者说从缓存中去获取视图,如果允许使用缓存,最终会把创建好的视图加入到缓存中,我们重点关注 createView 即可。

@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
	//是否允许缓存
	if (!this.isCache()) {
		//不允许 直接根据 viewName 和语言环境 创建一个 View 重点关注
		return this.createView(viewName, locale);
	} else {
		//允许缓存
		//根据 viewName 和语言环境 从缓存中获取 view
		Object cacheKey = this.getCacheKey(viewName, locale);
		View view = (View)this.viewAccessCache.get(cacheKey);
		//view 为 null 判断
		if (view == null) {
			//缓存没有获取到  同步锁 保证线程安全
			synchronized(this.viewCreationCache) {
				//再次去 viewCreationCache 缓存中获取
				view = (View)this.viewCreationCache.get(cacheKey);
				//view 为空判断
				if (view == null) {
					//还是为空 就创建一个 view  重点关注
					view = this.createView(viewName, locale);
					//为空判断  cacheUnresolved 默认为 true
					if (view == null && this.cacheUnresolved) {
						//赋值为空视图 是一个没有任何实现的视图
						view = UNRESOLVED_VIEW;
					}
					//view 不为空 加入缓存
					if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
						this.viewAccessCache.put(cacheKey, view);
						this.viewCreationCache.put(cacheKey, view);
					}
				}
			}
		} else if (this.logger.isTraceEnabled()) {
			this.logger.trace(formatKey(cacheKey) + "served from cache");
		}

		return view != UNRESOLVED_VIEW ? view : null;
	}
}

UrlBasedViewResolver#createView 方法源码分析

UrlBasedViewResolver#createView 方法会判断当前请求的类型,看是请求转发、重定向、普通请求的哪一种,不同请求类型会有不同的创建 View 的方法。

protected View createView(String viewName, Locale locale) throws Exception {
	//UrlBasedViewResolver 是否可以处理
	if (!this.canHandle(viewName, locale)) {
		return null;
	} else {
		//转发 url
		String forwardUrl;
		//视图名称是否是否 redirect: 打头
		if (viewName.startsWith("redirect:")) {
			//截取掉 redirect: 获取真正的 url
			forwardUrl = viewName.substring("redirect:".length());
			//创建一个重定向视图
			RedirectView view = new RedirectView(forwardUrl, this.isRedirectContextRelative(), this.isRedirectHttp10Compatible());
			//获取重定向 host
			String[] hosts = this.getRedirectHosts();
			//为 null 判断
			if (hosts != null) {
				//给视图设置 view 
				view.setHosts(hosts);
			}
			//应用给生命周期方法 重点关注
			return this.applyLifecycleMethods("redirect:", view);
		} else if (viewName.startsWith("forward:")) {
			//请求转发 获取转发 url
			forwardUrl = viewName.substring("forward:".length());
			//创建 InternalResourceView
			InternalResourceView view = new InternalResourceView(forwardUrl);
			//应用给生命周期方法 其实就是使用 ApplicationContext 完成 view 的初始化 重点关注
			return this.applyLifecycleMethods("forward:", view);
		} else {
			//不是 redirect  也不是 forward  就是普通视图 这里会调用 重点关注
			return super.createView(viewName, locale);
		}
	}
}

UrlBasedViewResolver#applyLifecycleMethods 方法源码分析

UrlBasedViewResolver#applyLifecycleMethods 应用给生命周期方法,其实就是使用 ApplicationContext 完成 View 的初始化 。

//org.springframework.web.servlet.view.UrlBasedViewResolver#applyLifecycleMethods
protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) {
	//获取 ApplicationContext
	ApplicationContext context = this.getApplicationContext();
	//为空 判断
	if (context != null) {
		//获取 beanFactory 完成 view 初始化
		Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName);
		//初始化完了 是否是 view 类型
		if (initialized instanceof View) {
			//返回 view
			return (View)initialized;
		}
	}

	return view;
}

AbstractCachingViewResolver#createView 方法源码分析

AbstractCachingViewResolver#createView 方法是普通请求创建 View 的方法,它调用的是父类 AbstractCachingViewResolver 的 createView 方法 ,该方法并没有什么实际操作,接着调用了 loadView 方法,loadView 方法完成了 View 的创建及初始化、属性见擦汗等。

//org.springframework.web.servlet.view.AbstractCachingViewResolver#createView
@Nullable
protected View createView(String viewName, Locale locale) throws Exception {
	return this.loadView(viewName, locale);
}


//org.springframework.web.servlet.view.UrlBasedViewResolver#loadView 
protected View loadView(String viewName, Locale locale) throws Exception {
	//根据viewName 构建view  重点关注
	AbstractUrlBasedView view = this.buildView(viewName);
	//完成view 初始化
	View result = this.applyLifecycleMethods(viewName, view);
	//view.checkResource(locale) 检查view属性 默认返回 true
	return view.checkResource(locale) ? result : null;
}

AbstractCachingViewResolver#buildView 方法源码分析

AbstractCachingViewResolver#buildView 方法获取 View 的 Class 类型,反射创建了 View,并给 View 设置了各种属性,是正真创建 View 的方法。

//org.springframework.web.servlet.view.UrlBasedViewResolver#buildView	
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
	//获取view 的class
	Class<?> viewClass = this.getViewClass();
	//断言判断 class 是否为 null
	Assert.state(viewClass != null, "No view class");
	//根据 view 的 class 创建一个 view
	AbstractUrlBasedView view = (AbstractUrlBasedView)BeanUtils.instantiateClass(viewClass);
	//根据前缀+名称+后缀 拼接成视图信息
	view.setUrl(this.getPrefix() + viewName + this.getSuffix());
	//view 设置 Attribute
	view.setAttributesMap(this.getAttributesMap());
	//获取 contentType
	String contentType = this.getContentType();
	if (contentType != null) {
		//view 设置 contentType
		view.setContentType(contentType);
	}
	//获取 RequestContextAttribute
	String requestContextAttribute = this.getRequestContextAttribute();
	if (requestContextAttribute != null) {
		//设置 RequestContextAttribute 
		view.setRequestContextAttribute(requestContextAttribute);
	}
	//是否暴露 PathVariables  Request请求中的 url 属性
	Boolean exposePathVariables = this.getExposePathVariables();
	if (exposePathVariables != null) {
		//是否将这些属性暴露在视图中
		view.setExposePathVariables(exposePathVariables);
	}
	//是否将 bean 暴露在视图中
	Boolean exposeContextBeansAsAttributes = this.getExposeContextBeansAsAttributes();
	if (exposeContextBeansAsAttributes != null) {
		//设置到视图中
		view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
	}
	//需要暴露的 bean name
	String[] exposedContextBeanNames = this.getExposedContextBeanNames();
	if (exposedContextBeanNames != null) {
		//设置到视图中
		view.setExposedContextBeanNames(exposedContextBeanNames);
	}
	//返回 视图
	return view;
}

AbstractView#render 方法源码分析

上文说了 DispatcherServlet#render 方法主要可以分为两步,分别是创建视图 View 和 解析渲染视图 view.render,创建 View 的步骤我们已经分析完了,我们来分析一下 view.render 方法,也就是 AbstractView#render 方法,该方法会将 Model、request、response 封装成成一个 Map 对象给后面的视图渲染使用,同时会对下载请求做一些处理,然后就开始进行视图渲染。

//org.springframework.web.servlet.view.AbstractView#render
public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
	if (this.logger.isDebugEnabled()) {
		this.logger.debug("View " + this.formatViewName() + ", model " + (model != null ? model : Collections.emptyMap()) + (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
	}
	//合并成一个 Map 对象
	Map<String, Object> mergedModel = this.createMergedOutputModel(model, request, response);
	//响应前处理 其实就是判断这是否是一个下载请求  默认不是下载请求
	this.prepareResponse(request, response);
	//渲染合并数据模型 重点关注
	this.renderMergedOutputModel(mergedModel, this.getRequestToExpose(request), response);
}

InternalResourceView#renderMergedOutputModel 方法源码分析

InternalResourceView#renderMergedOutputModel 方法就是进行视图渲染了,对于不同的请求类型有不同的渲染方式。

//org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
	//将模型的数据全部写去到 request 中 就是一个 request.setAttribute 的过程
	this.exposeModelAsRequestAttributes(model, request);
	//暴露一个助手 默认空实现 可以理解为一个扩展点
	this.exposeHelpers(request);
	//确定请求的路径
	String dispatcherPath = this.prepareForRendering(request, response);
	//获取可以用于 include、forward  的 RequestDispatcher
	RequestDispatcher rd = this.getRequestDispatcher(request, dispatcherPath);
	//为 null 判断
	if (rd == null) {
		//为 null 抛出异常
		throw new ServletException("Could not get RequestDispatcher for [" + this.getUrl() + "]: Check that the corresponding file exists within your web application archive!");
	} else {
		//判断当前是否为include请求
		//include方法使原先的 Servlet 和转发到的 Servlet 都可以输出响应信息 即原先的 Servlet 还可以继续输出响应信息
		if (this.useInclude(request, response)) {
			//是 include 请求
			//设置 ContentType
			response.setContentType(this.getContentType());
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Including [" + this.getUrl() + "]");
			}
			//include 包含的意思
			// 调用 include()方法进行文件引入
			rd.include(request, response);
		} else {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Forwarding to [" + this.getUrl() + "]");
			}
			//请求转发 直接使用 forward 请求将当前请求转发到目标文件路径中 渲染该视图
			rd.forward(request, response);
		}

	}
}

至此,视图渲染部分的核心流程已经分析完毕,其实整个流程就两个要点,一个是通过视图解析器去创建 View,一个就是 View 的渲染过程,视图渲染分析完毕,也代表着整个 Spring MVC 的工作流程分析完毕,源码告诉了我们 Spirng MVC 是怎样去处理一个请求的,希望可以帮助到有需要的小伙伴。

欢迎提出建议及对错误的地方指出纠正。

相关推荐

  1. SpringMVC分析(十)--消息转换器

    2024-06-08 00:44:02       32 阅读
  2. SpringMVC分析(八)--参数解析器

    2024-06-08 00:44:02       42 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-08 00:44:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-08 00:44:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-06-08 00:44:02       82 阅读
  4. Python语言-面向对象

    2024-06-08 00:44:02       91 阅读

热门阅读

  1. oracle dataguard 从库 MRP 进程的状态是 WAIT_FOR_GAP

    2024-06-08 00:44:02       31 阅读
  2. 如何评价GPT-4o?

    2024-06-08 00:44:02       27 阅读
  3. CEF编译打包(支持MP4播放,windows-x64版本)

    2024-06-08 00:44:02       23 阅读
  4. WebSocket和HTTP协议对比

    2024-06-08 00:44:02       30 阅读
  5. 【Git】(七)git push用法

    2024-06-08 00:44:02       26 阅读
  6. 中子介程三

    2024-06-08 00:44:02       29 阅读
  7. 智密腾讯云直播组建--客户端API简介

    2024-06-08 00:44:02       22 阅读
  8. 常见的api:Runtime Object

    2024-06-08 00:44:02       29 阅读
  9. MySQL查看和修改时区

    2024-06-08 00:44:02       26 阅读
  10. Spring的bean的生命周期

    2024-06-08 00:44:02       24 阅读
  11. C++中的智能指针

    2024-06-08 00:44:02       31 阅读
  12. LIMS系统在汽车第三方检测实验室的应用

    2024-06-08 00:44:02       35 阅读