java源码 - SpringMVC(2)之 请求过程

还是和前面一样分析HttpServletBean、FrameworkServlet和DispatcherServlet
书籍《看透Spring MVC》

1. HttpServletBean

HttpServletBean主要参与了创建工作,并没有涉及请求的处理。

2. FrameworkServlet

如果了解,Servlet与Tomcat,流程处理的起始是从Servlet接口的Service方法开始,然后在HttpServlet的service方法中根据请求的类型不同将请求路由到了doGet、doHead、doPost、doPut、doDelete、doOptions和doTrace七个方法,并且做了doHead、doOptions和doTrace的默认实现,其中doHead调用doGet,然后返回只有header没有body的response。

而我们知道,部署到Tomcat上的Servlet其实只有DispatcherServlet,然后由其分发请求。
在这里插入图片描述
看类图DispatcherServlet继承自FrameworkServlet,所以我们从FrameworkServlet说起。
看类图,我们得知FrameworkServlet 继承了HttpServletBean,并且也重写了service()方法。

	/**
	 * 控制父类实现以拦截补丁请求。
	 */
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
		if (httpMethod == HttpMethod.PATCH || httpMethod == null) {			//请求全部交给processRequest处理
			processRequest(request, response);
		}
		else {
			super.service(request, response);
		}
	}

重点关注processRequest内的doService,其在DispatcherServlet中具体实现:是一个模板方法。

/ * *
* 处理这个请求,发布一个事件,不管结果如何。
* 实际的事件处理是由抽象来执行的
* {@link #doService}模板方法
* /
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;
	//获取了LocaleContextHolder和RequestContextHolder中原来保存的LocaleContext和RequestAttributes,
	//设置到previousLocaleContext和previousAttributes临时属性
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		//调用buildLocaleContext和buildRequestAttributes方法获取到当前请求的LocaleContext和RequestAttributes
		LocaleContext localeContext = buildLocaleContext(request);

		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	//使用request拿到异步处理管理器并设置了拦截器
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
	//通过initContextHolders方法将它们设置到LocaleContextHolder和Request-ContextHolder中(处理完请求后再恢复到原来的值)
		initContextHolders(request, localeContext, requestAttributes);

		try {
		//重点关注doService,在DispatcherServlet中具体实现
			doService(request, response);
		}
		catch (ServletException | IOException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (Throwable ex) {
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		}

		finally {
		//(finally中)通过resetContextHolders方法将原来的previousLocaleContext和previousAttributes恢复到Locale-ContextHolder和RequestContextHolder中,
		//并调用publishRequestHandledEvent方法发布了一个ServletRequestHandledEvent类型的消息。
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
				requestAttributes.requestCompleted();
			}
			logResult(request, response, failureCause, asyncManager);
			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

在doService前后还做了一些事情(装饰模式);

LocaleContext里面存放着Locale(也就是本地化信息,如zh-cn等)
RequestAttributes是spring的一个接口,通过它可以get/set/removeAttribute,根据scope参数判断操作request还是session。

2.1 LocaleContextHolder

在这里插入图片描述

/ * *
与LocaleContext实例关联的简单holder类,当前线程。LocaleContext将被继承
由当前线程派生的任何子线程
*在Spring中用作当前语言环境的中心holder,
*必要时:例如,在MessageSourceAccessor中。
* DispatcherServlet在这里自动公开它的当前语言环境。
*其他应用程序也可以公开他们的类,使类喜欢
*MessageSourceAccessor自动使用该地区。
 */
public final class LocaleContextHolder {

在这里插入图片描述
LocaleContextHolder类里面封装了两个属性localeContextHolder和inheritableLocaleContextHolder,它们都是LocaleContext,其中第二个可以被子线程继承。LocaleContextHolder还提供了get/set方法,可以获取和设置LocaleContext,另外还提供了get/setLocale方法,可以直接操作Locale,当然都是static的。

2.2 RequestContextHolder

/**
Holder类以线程绑定的形式公开web请求
* {@link RequestAttributes}对象。请求将被继承
由当前线程派生的任何子线程
 * {@code inheritable} flag is set to {@code true}.
 *
 * <p>Use {@link RequestContextListener} or
 * {@link org.springframework.web.filter.RequestContextFilter} to expose
 * the current web request. Note that
 * {@link org.springframework.web.servlet.DispatcherServlet}
 * already exposes the current request by default.
 *
 */
public abstract class RequestContextHolder  {

在这里插入图片描述

/ * *
*访问与请求相关的属性对象的抽象。
*支持访问请求范围的属性和会话范围的属性
*属性,带有可选的“全局会话”概念。
*
*
可以实现任何类型的请求/会话机制,
*特别是servlet请求。
* /
public interface RequestAttributes {
/ * *
基于servlet的{@link RequestAttributes}接口实现。
*
*
从servlet请求和HTTP会话范围访问对象,
*没有“会话”和“全局会话”的区别。
*
*与javax . servlet . servletrequest @see # getAttribute
* @see javax.servlet.http.HttpSession # getAttribute
* /
public class ServletRequestAttributes extends AbstractRequestAttributes {

在这里插入图片描述
里面封装了RequestAttributes,可以get/set/removeAttribute,而且因为实际封装的是ServletRequestAttributes,所以还可以getRequest、getResponse、getSession。
故这也是为什么我们可以随意取出当前线程的request等引用的原因。


在方法最后的finally中调用resetContextHolders方法将原来的LocaleContext和Request-Attributes又恢复了。这是因为在Sevlet外面可能还有别的操作,如Filter(Spring-MVC自己的HandlerInterceptor是在doService内部的)等,为了不影响那些操作,所以需要进行恢复。

2.2 publishRequestHandledEvent

在这里插入图片描述
在这里插入图片描述
当publishEvents设置为true时,请求处理结束后就会发出这个消息,无论请求处理成功与否都会发布。
publishEvents可以在web.xml文件中配置Spring MVC的Servlet时配置,默认为true。

总结一下:

  1. 在service方法里添加了对PATCH的处理
  2. 将所有需要自己处理的请求都集中到了processRequest方法进行统一处理
  3. 在processRequest里面主要的处理逻辑交给了doService,这是一个模板方法
  4. 对使用当前request获取到的LocaleContext和RequestAttributes进行了保存,以及处理完之后的恢复
  5. 发布了ServletRequest-HandledEvent事件

3. DispatcherServlet

最核心的来了。
那么我们直接进入到doService():

/**	
	 * 将特定于dispatcherservlet的请求属性和委托暴露给{@link 		#doDispatch}
	*用于实际调度。
	 */
	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		logRequest(request);

		//保存请求属性的快照,以备包括,
	//能够恢复原始属性后,include。
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		// 使框架对象对处理程序和视图对象可用。
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

		if (this.flashMapManager != null) {
			FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
			if (inputFlashMap != null) {
				request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
			}
			request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
			request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
		}

		try {
		//核心代码
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				//在include的情况下恢复原始属性快照。
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

通过注释我们也知道,请求其实是交给了doDispatch进行具体的处理。在交给doDispatch进行具体的处理之前,如果是include的请求,则进行一次快照备份。
之后对request进行了一些属性设置,后面三个属性都和flashMap相关,主要用于Redirect转发时参数的传递。
我们知道重定向是没有传递参数的功能的,按普通的模式如果想传递参数,就只能将其写入url中,但是url有长度限制,另外有些场景中我们想传递的参数还不想暴露在url里,这时就可以用flashMap来进行传递了。
举例就是model,attr。
一般有三种:

  • 使用前面讲过的RequestContextHolder获取到request,并从其属性中拿到output-FlashMap,然后将属性放进去,RequestContextHolder获取request的方法。
  • 通过传入的attr参数的addFlashAttribute方法设置,这样也可以保存到outputFlashMap中,和第1种方法效果一样。
  • 通过传入的attr参数的addAttribute方法设置,这样设置的参数不会保存到FlashMap,而是会拼接到url中。

@RequestMapping("/model") public String modelex(Model model) { model.addAttribute("hello"); return "redirect:success"; }

接下来看doDispatch。
doDispatch任务很简单:
①根据request找到Handler;
②根据Handler找到对应的HandlerAdapter;
③用HandlerAdapter处理Handler;
④调用processDispatchResult方法处理上面处理之后的结果(包含找到View并渲染输出给用户)
对应如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里出现了很多的Handler,而Handler对SpringMVC有很重要的作用。

Handler:也就是处理器,它直接对应着MVC中的C也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法,如果你能想到别的表现形式也可以使用,它的类型是Object。我们前面例子中标注了@RequestMapping的所有方法都可以看成一个Handler。只要可以实际处理请求就可以是Handler。

HandlerMapping:是用来查找Handler的,在Spring MVC中会处理很多请求,每个请求都需要一个Handler来处理,具体接收到一个请求后使用哪个Handler来处理呢?这就是HandlerMapping要做的事情。

HandlerAdapter:也就是适配器。因为Spring MVC中的Handler可以是任意的形式,只要能处理请求就OK,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法(如doService方法)。怎么让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。

通过以上四句代码可以得出结论,使用HandlerMapping找到干活的Handler,找到使用Handler的HandlerAdapter,让HandlerAdapter使用Handler作用,最后将结果通过view展示。

3.1 doDispatch全解

注释如下:

将实际的分派处理程序处理。
通过依次应用servlet的处理程序映射来获得处理程序。
HandlerAdapter将通过查询servlet安装的HandlerAdapter获得
找到第一个支持处理程序类的。
所有的HTTP方法都由此方法处理。这取决于处理适配器或处理程序
他们自己决定哪些方法是可以接受的。

代码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
			//检查是否是上传请求
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 根据request确定当前请求的处理程序。
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// 确定当前请求的处理程序适配器。
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// 处理最后修改头,如果支持的处理程序。
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// 实际调用处理程序。
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				//在4。3版本中,我们也在处理处理器方法抛出的错误,
				//使它们可用于@ExceptionHandler方法和其他场景。
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// 而不是完成后和完成后
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// 清除由多部分请求使用的任何资源。
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

其实,我们不看代码已经可以猜到dodispatch该做一些什么工作了:处理请求与结果处理。
在方法刚开始时,声明了几个变量:

  • HttpServletRequest processedRequest:实际处理时所用的request,如果不是上传请求则直接使用接收到的request,否则封装为上传类型的request。
  • HandlerExecutionChain mappedHandler:处理请求的处理器链(包含处理器和对应的Interceptor)。
  • boolean multipartRequestParsed:是不是上传请求的标志。
  • ModelAndView mv:封装Model和View的容器,此变量在整个Spring MVC处理的过程中承担着非常重要角色,如果使用过Spring MVC就不会对ModelAndView陌生。
  • Exception dispatchException:处理请求过程中抛出的异常。需要注意的是它并不包含渲染过程抛出的异常。

接着,就检查请求是否时上传请求。
在这里插入图片描述
如果是,将request转换为Multi-partHttpServletRequest,并将multipartRequestParsed标志设置为true。

/ * *
*将请求转换为多部分请求,并提供多部分解析器。
*如果没有设置多部分解析器,只需使用现有的请求。
* @param请求当前HTTP请求
* @返回处理后的请求(如果需要,可以使用多部分包装器)
* @see MultipartResolver # resolveMultipart
* /
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {

接着,
在这里插入图片描述
通过getHandler方法获取Handler处理器链,其中使用到了HandlerMapping,返回值为HandlerExecutionChain类型,其中包含着与当前request相匹配的Interceptor和Handler。

/ * *
*返回此请求的HandlerExecutionChain。
*
依次尝试所有处理程序映射。
* @param请求当前HTTP请求
* @返回HandlerExecutionChain,如果没有找到处理程序,则返回{@code null}
* /
	@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {

接下来处理处理GET、HEAD请求的Last-Modified。
在这里插入图片描述

当浏览器第一次跟服务器请求资源(GET、Head请求)时,服务器在返回的请求头里面会包含一个Last-Modified的属性,代表本资源最后是什么时候修改的。在浏览器以后发送请求时会同时发送之前接收到的Last-Modified,服务器接收到带Last-Modified的请求后会用其值和自己实际资源的最后修改时间做对比,如果资源过期了则返回新的资源(同时返回新的Last-Modified),否则直接返回304状态码表示资源未过期,浏览器直接使用之前缓存的结果。

接着调用调用相应Interceptor的preHandle。也就是HandlerAdapter使用Handler处理请求。
在这里插入图片描述
在这里插入图片描述
prehandler处理
在这里插入图片描述
再接着,判断是否需要异步处理:
在这里插入图片描述
如果不需要异步处理,然后执行相应Interceptor的postHandle。
在这里插入图片描述
接下来使用processDispatchResult方法处理前面返回的结果,其中包括处理异常、渲染页面、触发Interceptor的afterCompletion方法三部分内容。
在这里插入图片描述
至此, doDispatch就差不多结束了。

3.2 doDispatch流程图解

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值