springMVC请求调用流程浅析

前言

Servlet中的service方法用于应答浏览器请求,每次请求都会调用该方法。DispatcherServlet也是一个Servlet。其继承图如下
在这里插入图片描述

一、请求的分发

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
    @Override
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
		//判断是不是patch请求 或者httpMethod为null
		if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
			processRequest(request, response);
		}
		else {
		    //如果不是patch请求,底层也是调用processRequest方法
		    //因为FrameworkServlet重写了如doPost的方法
			super.service(request, response);
		}
	}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		long startTime = System.currentTimeMillis();
		// 记录抛出的异常~~~(若有的话)
		Throwable failureCause = null;
		
		//拿到之前的LocaleContext上下文(因为可能在Filter里已经设置过了)
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		// 以当前的request创建一个Local的上下文,后面会继续处理
		LocaleContext localeContext = buildLocaleContext(request);

		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		// 这里面build逻辑注意:previousAttributes若为null,或者就是ServletRequestAttributes类型,
		//那就new ServletRequestAttributes(request, response);
		// 若不为null,就保持之前的绑定结果,不再做重复绑定了(尊重原创)
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

		// 拿到异步管理器。这里是首次获取,会new WebAsyncManager(),然后放到request的attr里面
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		//这里需要注意:给异步上下文恒定注册了RequestBindingInterceptor这个拦截器(作用:绑定当前的request、response、local等)
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

		//这句话很明显,就是吧request和Local上下文、RequestContext绑定
		initContextHolders(request, localeContext, requestAttributes);

		try {
		    //模版设计模式:由子类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 {
		    //这个时候已经全部处理完成,视图已经渲染了
			//doService()方法完成后,重置上下文,也就是解绑
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
				requestAttributes.requestCompleted();
			}
		    //日志
			logResult(request, response, failureCause, asyncManager);
			//关键:不管执行成功与否,都会发布一个ServletRequestHandledEvent事件
			//说我处理了这个请求(有需要监听的,就可以监听这个事件了,每次请求都会有)
			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

publishRequestHandledEvent()发布请求处理完后的事件源码

private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
			long startTime, @Nullable Throwable failureCause) {
		//当publishEvents设置为true和 webApplicationContext 不为空就会处理这个事件的发布
		if (this.publishEvents && this.webApplicationContext != null) {
			// 计算出处理该请求花费的时间
			long processingTime = System.currentTimeMillis() - startTime;
			this.webApplicationContext.publishEvent(
					//ServletRequestHandledEvent这个事件:目前来说只有这里会发布
					new ServletRequestHandledEvent(this,
							request.getRequestURI(), request.getRemoteAddr(),
							request.getMethod(), getServletConfig().getServletName(),
							WebUtils.getSessionId(request), getUsernameForRequest(request),
							processingTime, failureCause, response.getStatus()));
		}
	}

下面我们来写个监听器,专门来监听这个事件:

//专门监听ServletRequestHandledEvent时间的监听器
@Slf4j
@Component
public class ServletReqestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent> {

    @Override
    public void onApplicationEvent(ServletRequestHandledEvent event) {
        //url=[/demowar_war/controller/hello]; client=[127.0.0.1]; method=[GET]; servlet=[dispatcher]; session=[null]; user=[null]; time=[143ms]; status=[OK]
        log.info(event.getDescription()); 
        log.info("返回状态码为:" + event.getStatusCode()); //返回状态码为:200
        log.info("异常信息为:" + event.getFailureCause()); //异常信息为:null
        log.info("处理请求耗时为:" + event.getProcessingTimeMillis()); //处理请求耗时为:143
        log.info("事件源为:" + event.getSource()); //事件源为:org.springframework.web.servlet.DispatcherServlet@3e7fadbb
    }
}

二、请求的处理

DispatcherServlet#doDispatch方法解析
首先根据请求的路径找到HandlerMethod(带有Method反射属性,也就是对应Controller中的方法),
然后匹配路径对应的拦截器,有了HandlerMethod和拦截器构造个HandlerExecutionChain对象。HandlerExecutionChain对象的获取是通过HandlerMapping接口提供的方法中得到。
有了HandlerExecutionChain之后,通过HandlerAdapter对象进行处理得到ModelAndView对象,HandlerMethod内部handle的时候,使用各种HandlerMethodArgumentResolver实现类处理HandlerMethod的参数(非常重要),使用各种HandlerMethodReturnValueHandler实现类处理返回值。 最终返回值被处理成ModelAndView对象,这期间发生的异常会被HandlerExceptionResolver接口实现类进行处理。

	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {

		// 如果该请求是include的请求(请求包含) 那么就把request域中的数据保存一份快照版本
		// 等doDispatch结束之后,会把这个快照版本的数据覆盖到新的request里面去
		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));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
		// 说得很清楚,把一些常用对象放进请求域  方便Handler里面可以随意获取
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); //这个是web子容器哦
		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 {
			// DispatcherServlet最重要的方法,交给他去分发请求你、找到handler处理等等
			doDispatch(request, response);
		} finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// Restore the original attribute snapshot, in case of an include.
				//如果是include请求  会上上面的数据快照,重新放置到request里面去
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //此处用processedRequest  需要注意的是:若是处理上传,processedRequest 将和request不再指向同一对象
		HttpServletRequest processedRequest = request;
		// 异常链处理器
		HandlerExecutionChain mappedHandler = null;
		//是否文件上传的标志位
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

			try {
			    //checkMultipart 这个方法很重要,判断是否是上传需求。且看下面的具体分析:::
				//如果请求是POST请求,并且请求头中的Context-Type是以multipart/开头的就认为是文件上传的请求
				processedRequest = checkMultipart(request);
				// 标记一下:是否是文件上传的request了
				multipartRequestParsed = (processedRequest != request);

			    //解析请求链接,然后根据请求链接找到执行这个请求的类
			    //(HandlerMapping所说的handler,也就是我们写的Controller或是Action)
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
				   //如果没有找到对应的Handler,返回一个404异常
					noHandlerFound(processedRequest, response);
					return;
				}

				// 根据实际的handler去找到一个合适的HandlerAdapter,方法详细逻辑同getHandler,因此不再解释
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// 如果是GET请求,如果内容没有变化的话,则直接返回
				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;
				}

				// 利用适配器HandlerAdapter处理当前Handler
				// 真正执行我们自己书写的controller方法的逻辑。返回一个ModelAndView
				// 这也是一个很复杂的过程(序列化、数据绑定等等),需要后面专题讲解
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                // 如果异步启动了,这里就先直接返回了,也就不会再执行拦截器PostHandle之类的
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
                //意思是:如果我们没有设置viewName,就采用默认的。否则采用我们自己的
				applyDefaultViewName(processedRequest, mv);
				// 执行所有的拦截器的postHandle方法,并且把mv给他
				// 这里有一个小细节:这个时候拦截器是【倒序】执行的
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				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()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值