背景
本文作为 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对象,流程图如下所示:
https://img-blog.csdnimg.cn/e747503a399846acbb1db5d4ea5a3fcd.png" alt="在这里插入图片描述" />
总之,当Http请求到达Tomcat连接池后,会将请求消息封装成(HttpRequest/HttpResponse对象)
, 通过调用Servlet标准接口实现消息的传递。
SpringMVC框架对应的Servlet对象为DispatcherServlet,即调用栈会进入DispatcherServlet的void service(ServletRequest req, ServletResponse res)
方法。
因此,有必要了解一下DispatcherServlet类的继承关系以及对Servlet方法实现情况,如下图所示:
https://img-blog.csdnimg.cn/9df482b0bda64ad8b8bee4eac255f589.png" alt="![在这里" />
2.HttpServlet
https://img-blog.csdnimg.cn/20f879b2100f457eacbb553dcb56f8cf.png" alt="在这里插入图片描述" />
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
https://img-blog.csdnimg.cn/522bb2943570475f8bde69dc1e04a32b.png" alt="在这里插入图片描述" />
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
https://img-blog.csdnimg.cn/d076738dc1fa4bd386fdfd8f9ac123a2.png" alt="在这里插入图片描述" />
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);
}
}
上述逻辑可以表示为:
https://img-blog.csdnimg.cn/d5a366ed42c9442b8bfee93b703606de.png" alt="【插图】" />
拦截器的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方法。
https://img-blog.csdnimg.cn/1a9c3e03902840e6b85724741629311e.png" alt="在这里插入图片描述" />
涉及的代码逻辑如下:
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方法,流程如下所示:
https://img-blog.csdnimg.cn/97ff068dc9e64785bed95786022ac92a.png" alt="在这里插入图片描述" />