SpringMVC系列-2 HTTP请求调用链

背景

本文作为 SpringMVC系列 第二篇,介绍HTTP请求的调用链:从请求进入Tomcat到数据流返回客户端的完整过程。为了尽可能把流程表达清楚,进行了很多减支处理,只关注主线逻辑。

本文也作为SpringMVC系列后续文章的基础,在调用链梳理清楚的基础上,后文对重要逻辑分别进行展开介绍,如拦截器、异常处理器、转换器、消息转换器、异步请求、文件上传等。在这些文章完成后,会出一个Spring框架应用专题,包括:结果集框架、错误码框架、鉴权逻辑、分页查询、事件框架等,基于此会对Spring系列和SpringMVC系列文章有更深层次的理解。

1.调用链

Tomcat从逻辑上可以分为连接器(coyote)和Servlet容器(catalina)两个部分:coyote负责接收客户端的请求,并按照协议对请求进行解析,封装成Java对象后发送给catalina以及将catalina返回的消息推送给客户端;catalina提供了Servlet容器实现,负责处理具体的请求并进行响应。
其中,coyote封装了底层的网络通讯(Socket),为catalina提供了统一的接口(Request/Response对象)而与Servlet容器解耦;catalina内部通过适配器将(Request/Response对象)转换为(HttpRequest/HttpResponse对象),然后将消息发送给Servlet对象,流程图如下所示:
在这里插入图片描述
总之,当Http请求到达Tomcat连接池后,会将请求消息封装成(HttpRequest/HttpResponse对象), 通过调用Servlet标准接口实现消息的传递。
SpringMVC框架对应的Servlet对象为DispatcherServlet,即调用栈会进入DispatcherServlet的void service(ServletRequest req, ServletResponse res)方法。
因此,有必要了解一下DispatcherServlet类的继承关系以及对Servlet方法实现情况,如下图所示:
![在这里

2.HttpServlet

在这里插入图片描述

DispatcherServlet关于Servlet:void service(ServletRequest req, ServletResponse res)接口的实现逻辑在HttpServlet类中,代码如下:

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
      HttpServletRequest  request;
      HttpServletResponse response;
      try {
          request = (HttpServletRequest) req;
          response = (HttpServletResponse) res;
      } catch (ClassCastException e) {
          throw new ServletException(lStrings.getString("http.non_http"));
      }
      service(request, response);
  }

逻辑较为简单,直接将ServletRequest/ServletResponse对象转为HttpServletRequest/HttpServletResponse,并调用service(HttpServletRequest req, HttpServletResponse resp)接口。后者中根据HTTP方法类型派发给了doGet/doPost/doPut等接口;而doGet/doPost/doPut等接口的实现逻辑在FrameworkServlet中归一到void processRequest(HttpServletRequest request, HttpServletResponse response)接口中:

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	processRequest(request, response);
}

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	processRequest(request, response);
}

@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	processRequest(request, response);
}

总之,所有来自Servlet的请求都会进入processRequest方法中进行处理。

3.FrameworkServlet

在这里插入图片描述

processRequest方法的主线逻辑如下:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
	LocaleContext localeContext = buildLocaleContext(request);

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

	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

	initContextHolders(request, localeContext, requestAttributes);

	try {
		doService(request, response);
	} finally {
		resetContextHolders(request, previousLocaleContext, previousAttributes);
	}
}

上述代码在逻辑上可以分为三块:调用doService(request, response)接口处理并响应、调用前的准备工作、调用后的清理工作。
doService接口作为主体逻辑在下一节中进行介绍,本节对准备工作和清理工作进行介绍。

准备工作

LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);

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

// WebAsyncManager提供了HTTP请求异步处理能力,不是本文的重点
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

根据request对象获取Locale对象并将其通过ThreadLocal对象中,将request和response对象封装在RequestAttributes对象中,保存在ThreadLocal对象中;借助ThreadLocal的能力,对外提供了静态方法,使得程序在任意时刻都取得Locale对象、request和response对象。

清理工作

清理工作对应从内存中将该线程相关的Locale对象和request和response对象从内存中清除,以防止内存泄露。

resetContextHolders(request, previousLocaleContext, previousAttributes);

private void resetContextHolders(HttpServletRequest request, LocaleContext prevLocaleContext, RequestAttributes previousAttributes) {
	LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);
	RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable);
}

这是一个使用ThreadLocal基本的套路,后面介绍Spring应用专题时会涉及。

4.DispatchServlet

在这里插入图片描述

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	// springMVC容器
	request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
	
	// locale解析器
	request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
	
	// 主体相关
	request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
	request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

	// flashMapManager在后续介绍转发和重定向内容时进行介绍,这里暂时忽略
	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 {
		// 清理工作 
}

doService逻辑比较简单,包括:调用doDispatch方法前对request的属性设置,以及在调用之后清理工作。doDispatch方法是核心,其他逻辑是在为调用该方法做的准备或收尾工作。
本文希望将HTTP调用链的主线逻辑表达清楚,因此会省去异步请求(WebAsyncManager相关)、文件上传(MultipartResolver相关)、异常捕获等逻辑,如下所示:

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

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

		try {
			mappedHandler = getHandler(request);
			if (mappedHandler == null) {
				noHandlerFound(request, response);
				return;
			}
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
			if (!mappedHandler.applyPreHandle(request, response)) {
				return;
			}
			mv = ha.handle(request, response, mappedHandler.getHandler());
			mappedHandler.applyPostHandle(request, response, mv);
		} catch (Exception ex) {
			dispatchException = ex;
		}
		processDispatchResult(request, response, mappedHandler, mv, dispatchException);
	} catch (Exception ex) {
		triggerAfterCompletion(request, response, mappedHandler, ex);
	} 
}

上述逻辑可以表示为:
【插图】

拦截器的preHandle方法按照拦截器在列表中的顺序正向执行,postHandle和afterCompletion反向执行。

上图可以分为以下几个步骤:
(1)根据request对象获取执行链HandlerExecutionChain;执行链由Interceptor(拦截器)和Handler(对controller的包装)组成;
(2)根据Handler获取HandlerAdapter;
(3)依次调用拦截器的preHandle方法;
(4)借助HandlerAdapter 通过反射调用目标方法(controller对应的方法);
(5)依次调用拦截器的postHandle方法;
(6)对结果或者异常进行后置处理;
(7)依次调用拦截器的afterCompletion方法;

上述步骤为正常执行流程,异常场景包括:

异常场景1:
当根据equest对象获取执行链HandlerExecutionChain失败时(没有匹配项),则直接想客户端返回404。

异常场景2:
调用某个拦截器的preHandle方法返回false时,则反向执行已执行过拦截器的afterCompletion方法,并跳出doDispatch方法。
在这里插入图片描述
涉及的代码逻辑如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	//...
	if (!mappedHandler.applyPreHandle(processedRequest, response)) {
		return;
	}
	//...
}

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
	for (int i = 0; i < this.interceptorList.size(); i++) {
		HandlerInterceptor interceptor = this.interceptorList.get(i);
		if (!interceptor.preHandle(request, response, this.handler)) {
			triggerAfterCompletion(request, response, null);
			return false;
		}
		this.interceptorIndex = i;
	}
	return true;
}

当有拦截器的preHandle方法返回false时,调用triggerAfterCompletion(request, response, null)方法:

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
	for (int i = this.interceptorIndex; i >= 0; i--) {
		HandlerInterceptor interceptor = this.interceptorList.get(i);
		try {
			interceptor.afterCompletion(request, response, this.handler, ex);
		} catch (Throwable ex2) {
			logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
		}
	}
}

倒序执行已执行过preHandle的拦截器。
异常场景3:
preHandle或者反射调用目标方法(Controller接口)过程中有异常抛出时,经过异常捕获将异常包装成Exception对象,并通过processDispatchResult方法处理结果:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	try {
		//。。。
	} catch (Exception ex) {
		dispatchException = ex;
	} catch (Throwable err) {
		dispatchException = new NestedServletException("Handler dispatch failed", err);
	}
	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

processDispatchResult的主线逻辑如下:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
	if (exception != null) {
		mv = processHandlerException(request, response, mappedHandler.getHandler(), exception);
	}
	mappedHandler.triggerAfterCompletion(request, response, null);
}

如果有异常抛出,则exception不为空,通过processHandlerException方法处理异常场景。
如论是否有异常抛出,最后都通过mappedHandler.triggerAfterCompletion会触发拦截的afterCompletion方法。
同样需要注意:拦截器在执行preHandle过程中抛出的异常,则会反向执行已执行过preHandle方法拦截器的afterCompletion方法。
如果在拦截器的postHandle或者反射调用目标方法过程中抛出的异常,则会执行所有拦截器的afterCompletion方法,流程如下所示:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值