Spring MVC 请求执行流程的源码深度解析两万字
Posted L-Java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC 请求执行流程的源码深度解析两万字相关的知识,希望对你有一定的参考价值。
基于最新Spring 5.x,详细介绍了Spring MVC 请求的执行流程源码,给出了更加详细的Spring MVC请求执行流程步骤总结,以及详细的执行流程图。
我正在参与CSDN《新程序员》有奖征文,活动地址:https://marketing.csdn.net/p/52c37904f6e1b69dc392234fff425442。
此前,我们学习了Spring MVC项目启动初始化过程中的部分重要源码,Spring MVC项目启动之后,便能够接受请求、处理请求。
此前,我们已经学习了Spring MVC的请求执行流程,但实际上在Spring MVC请求执行流程过程中,要做的事儿有很多,比图CORS配置、参数转换等等,现在让我们从源码的角度再一次深入了解Spring MVC 请求的执行流程。
在文章的最后我们也给出了更加详细的Spring MVC请求执行流程步骤总结,以及详细的执行流程图,嫌弃源码太长的小伙伴可以直接跳到末尾。
下面的源码版本基于5.2.8.RELEASE
。
Spring MVC源码 系列文章
Spring MVC 初始化源码(1)—ContextLoaderListener与父上下文容器的初始化
Spring MVC 初始化源码(2)—DispatcherServlet与子容器的初始化以及MVC组件的初始化【一万字】
Spring MVC 初始化源码(3)—<mvc:annotation-driven >配置标签的源码解析
Spring MVC 初始化源码(4)—@RequestMapping注解的源码解析
文章目录
- Spring MVC源码 系列文章
- 1 源码入口
- 2 FrameworkServlet#service入口
- 3 processRequest处理请求
- 4 doDispatch分发请求
- 5 总结
1 源码入口
Spring MVC是对原始Servlet的封装,虽然我们在开发过程中不会再接触到Servlet级别的API,但是我们知道Spring MVC中有一个核心的Servlet实现,那就是DispatcherServlet,它作为核心控制器,用于接收任何的请求并将请求转发给对应的处理组件。因此,Spring MVC的请求处理入口仍然可以从DispatcherServlet中找到。
DispatcherServlet的uml类图如下:
可以看到,从它间接的继承了HttpServlet,因此Spring MVC的请求源码入口同样是HttpServlet#service()
方法!
这个service方法中并没有做太多的事情,主要是将ServletRequest和ServletResponse
强转转换为HttpServletRequest和HttpServletResponse
,最后会调用另一个service
方法,该方法才是真正的核心处理请求的方法。
HttpServlet自己的service
方法源码如下。其内部时一系列的以do开头的模版方法,回顾一下,在早期原始的Servlet项目
中,我们所要开发的就是这些doGet、doPost
方法:
并且,如果我们足够心细,我们能发现FrameworkServlet直接重写了HttpServlet的整个service方法
,此前的原始Servlet开发
的我们都是开发的do开头的方法
,比如doGet、doPost
,但是Spring MVC将整个service方法
都重写了,我们一起来看看。
2 FrameworkServlet#service入口
FrameworkServlet#service
方法可以看作Spring MVC一次请求的处理入口方法。
FrameworkServlet重写父类HttpServlet的service方法
的原因在注释上说的很明白了,就是为了支持PATCH
请求。
重写的方法的逻辑比较简单,首先解析出当前请求的方法,如果是PATCH
方法或者没有请求方法,则直接调用processRequest
方法处理该请求,否则,对于其他请求方法,则还是会调用父类HttpServlet的service来处理
,最终,对于不同的请求方法,还是会调用各自对应的do开头的模版方法
来处理。
/**
* FrameworkServlet的方法
* <p>
* 重写父类HttpServlet的service实现用以拦截PATCH请求。
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取当前请求的请求方法,可能是:GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
//如果是PATCH请求,或者请求方法为null,则另外处理
//PATCH方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新,目前用的比较少
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
//直接调用processRequest方法处理
processRequest(request, response);
} else {
//对于其他请求方法,则还是会调用父类HttpServlet的service来处理
//最终,对于不同的请求方法,还是会调用各自对应的do开头的模版方法来处理
super.service(request, response);
}
}
很明显,do开头的一系列方法也都由DispatcherServlet及其父类
帮我们都实现好了,因为在使用Spring MVC框架的时候我们并没有编写这些底层方法的实现。实际上,do开头的一系列模版方法,也是由FrameworkServlet
帮我们实现的,并且我们能够发现它实现的这些方法最终都会指向processRequest方法
。
从这里就能看出processRequest
方法的重要性了,继续向下看!
3 processRequest处理请求
processRequest
方法位于FrameworkServlet
中,提供了处理请求方法的骨架实现,并且留出了一系列模版方法
让子类去实现自己的逻辑,这是模版方法模式的应用。
该方法会将本次请求的LocaleContext和RequestAttributes
绑定到LocaleContextHolder和RequestContextHolder
的线程本地变量属性中,这样在当前请求处理过程中就可以在其他地方直接获取本次请求的request、response
等对象,比如:HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
,注意需要在当前请求线程中才能获取到。
请求处理完毕之后,无论成功与否
,都会调用publishRequestHandledEvent
方法发布一个ServletRequestHandledEvent
事件,表示该请求处理完毕,我们可以监听
该事件,并做出不同的处理。
该方法中的核心处理请求的方法就是doService
模版方法,该方法留给子类比如DispatcherServlet
实现。
/**
* FrameworkServlet的方法
* <p>
* 处理此请求,发布事件,无论结果如何。
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//当前时间毫秒
long startTime = System.currentTimeMillis();
//失败原因(异常)
Throwable failureCause = null;
//获取之前可能存在的LocaleContext,可能在Filter中设置的
//LocaleContext中可以获取当前的语言环境Locale
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//根据当前request构建LocaleContext,就是new 一个SimpleLocaleContext
LocaleContext localeContext = buildLocaleContext(request);
//获取之前可能存在的RequestAttributes,可能在Filter中设置的
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//根据当前request、response、previousAttributes构建新的ServletRequestAttributes
//如果此前的previousAttributes不为null,那么就还是使用这个对象
//否则就通过request和response直接new 一个ServletRequestAttributes,因此RequestAttributes中可以获取request和response
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
//获取异步请求管理器,新建一个WebAsyncManager对象,并且通过setAttribute存入request的属性中
//属性名为org.springframework.web.context.request.async.WebAsyncManager.WEB_ASYNC_MANAGER
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
//注册RequestBindingInterceptor这个拦截器,该拦截器可以初始化或者重置LocaleContext和RequestAttributes
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//初始化LocaleContext和RequestAttributes绑定到LocaleContextHolder和RequestContextHolder的线程本地变量属性中
//这样在当前请求处理过程中就可以在其他地方直接获取本次请求的request、response等对象,比如:
//HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
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 {
//最终,将LocaleContext和RequestAttributes从LocaleContextHolder和RequestContextHolder的线程本地变量属性中解除绑定
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
//发出请求已完成的信号。
//将会执行所有请求销毁回调,并更新在请求处理期间已访问的会话属性,最后将requestActive标志改为false
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
/*
* 发布一个事件,表示该请求处理完毕,无论成功与否
* 我们可以监听该事件,并做出不同的处理
*/
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
3.1 doService处理请求
doService
是处理请求的核心模版方法
,该方法留给子类比如DispatcherServlet
实现。
DispatcherServlet的doService方法
主要是将DispatcherServlet的一些属性
放置在当前请求的属性中,方便后续直接从request
中获取,并委托doDispatch
方法进行实际请求处理
,因此实际的真正请求处理还得看doDispatch
方法(是不是觉得调用层次很深呢?)。
//DispatcherServlet的属性
/**
* 包含请求执行完毕后执行请求属性的清理吗,默认清理
*/
private boolean cleanupAfterInclude = true;
/**
* DispatcherServlet的默认策略属性以其开头的公共前缀。
*/
private static final String DEFAULT_STRATEGIES_PREFIX = "org.springframework.web.servlet";
/**
* 保存当前的Web应用程序上下文的请求属性
*/
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";
/**
* 保存当前的LocaleResolver的请求属性,可通过视图检索。
*/
public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";
/**
* 保存当前的ThemeResolver的请求属性,可由视图检索。
*/
public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";
/**
* 保存当前的ThemeSource的请求属性,可通过视图检索。
*/
public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";
/**
* DispatcherServlet的方法
* <p>
* 公开特定于DispatcherServlet的请求属性,并委托doDispatch进行实际请求处理。
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
//如果是包含请求(include,即请求包含),那么保存request 属性快照,以便能够在包含请求处理完毕之后恢复原始属性
//也就是说,在包含请求处理过程中设置的request 属性将不会被保留
Map<String, Object> attributesSnapshot = null;
//判断是否是请求包含,尝试从request的属性中获取名为javax.servlet.include.request_uri的属性
//如果存在该属性,那么就是包含请求,否则就不是
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
//获取属性名
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
//默认将所有属性存入快照map中
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
//将一些对象存入request的属性中,方便handler和view中使用
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());
//flashMap管理器,默认为SessionFlashMapManager
//FlashMap可用于将属性从一个请求传递到另一个请求,通常是用在重定向中。
if (this.flashMapManager != null) {
//查找由与当前请求匹配的先前请求保存的FlashMap,将其从基础存储中删除,还删除其他过期的FlashMap实例。
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()) {
//如果是包含请求,那么还原原始属性快照。
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
3.2 publishRequestHandledEvent发布请求处理完毕事件
在processRequest方法的finally块的最后,将会通过当前DispatcherServlet关联的webApplicationContext发布一个ServletRequestHandledEvent事件,表示该请求处理完毕,无论成功与否。
ServletRequestHandledEvent中包含了本次请求的各种信息,我们可以监听该事件,并做出不同的处理。
//FrameworkServlet的属性
/**
* 我们是否应该在每个请求的末尾发布ServletRequestHandledEvent?
* 默认是应该的,但可以通过该参数关闭
*/
private boolean publishEvents = true;
/**
* 此Servlet关联的WebApplicationContext,在此前的initServletBean方法中被初始化
*/
@Nullable
private WebApplicationContext webApplicationContext;
/**
1. FrameworkServlet的方法
2. <p>
3. 发布一个事件,表示该请求处理完毕,无论成功与否
4. 5. @param request 当前request
6. @param response 当前response
7. @param startTime 请求处理开始时间戳毫秒
8. @param failureCause 失败原因(抛出的异常,可能为null)
*/
private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
long startTime, @Nullable Throwable failureCause) {
//如果允许发布事件并且关联的IoC容器不为null,那么无论我们是否成功,都发布一个事件。
if (this.publishEvents && this.webApplicationContext != null) {
//计算请求处理花费的时间
long processingTime = System.currentTimeMillis() - startTime;
//通过IoC容器发布ServletRequestHandledEvent事件,包含各种请求信息
//这样我们就可以使用Spring的事件监听机制来监听这个事件进而来监听这个请求了
this.webApplicationContext.publishEvent(
//this:事件源,当前DispatcherServlet对象
new ServletRequestHandledEvent(this,
//请求路径、ip地址
request.getRequestURI(), request.getRemoteAddr(),
//请求方法、ServletName
request.getMethod(), getServletConfig().getServletName(),
//SessionId、username
WebUtils.getSessionId(request), getUsernameForRequest(request),
//请求处理时间、失败原因、响应状态码
processingTime, failureCause, response.getStatus()));
}
}
4 doDispatch分发请求
将请求实际分派给对应的handler并且调用不同的组件进行请求处理。该方法的逻辑实际上就是Spring MVC请求处理的主体流程。
大概逻辑为:
- 调用
getHandler
方法确定当前请求的处理器——handler。 - 调用
getHandlerAdapter
方法根据handler确定当前请求的处理器适配器——HandlerAdapter。 - 调用
applyPreHandle
方法,顺序应用拦截器链中的此前找到的所有拦截器的PreHandle
预处理方法。全部通过则执行后续步骤,不通过则倒序执行已通过的拦截器的afterCompletion
方法,随后直接返回。 - 调用
handle
方法,通过HandlerAdapter使用给定的handler实际处理请求,返回一个ModelAndView结果对象。 - handler正常处理完毕时,调用
applyPostHandle
方法,倒序应用拦截器链中的此前找到的所有拦截器的postHandle
后处理方法。 - 调用
processDispatchResult
方法,处理执行handler的结果。主要目的是处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图。 - 最后无论请求处理成功还是失败抛出异常,都会调用
triggerAfterCompletion
方法,倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion
处理方法,表示请求处理完毕。
/**
* DispatcherServlet的方法
* <p>
* 将请求实际分派给对应的handler进行处理。
* <p>
* 该handler将通过依次应用servlet的HandlerMappings来获得,并且通过查询Servlet的
* handlerAdapters来查找支持该handler的第一个HandlerAdapter,从而获得HandlerAdapter。
* 所有HTTP方法都由该方法处理,由HandlerAdapters或handler本身来决定可接受的方法。
*
* @param request 当前 HTTP request
* @param response 当前 HTTP response
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//processedRequest初始化为当前request
HttpServletRequest processedRequest = request;
//已匹配的handler,也就是Handler执行链
HandlerExecutionChain mappedHandler = null;
//是否是已解析的多部件请求,即文件上传请求
boolean multipartRequestParsed = false;
//异步请求管理器
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
//模型和视图对象,handler执行的返回结果,内部包含了model和view对象
ModelAndView mv = null;
//记录异常
Exception dispatchException = null;
try {
/*
* 将请求使用多部分解析器转换为多部分请求,如果未设置多部分解析器或者当前请求不是多部分请求,则返回原始请求。
* 对于CommonsMultipartResolver,它底层走的是ApacheCommons FileUpload的文件上传逻辑,因此需要引入依赖
* 它会检查如果是POST请求并且content-type是"multipart/"前缀,则算作文件上传请求
* 则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)被解析为一个DefaultMultipartHttpServletRequest对象
*
* 对于StandardServletMultipartResolver,它是走的Servlet 3.0多部分请求解析的逻辑,不需要引入额外依赖
* 它会检查请求的content-type如果是"multipart/"前缀,则算作文件上传请求
* 则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)对象被解析为一个StandardMultipartHttpServletRequest对象
*/
processedRequest = checkMultipart(request);
//判断是否是多部件请求,即文件上传请求
multipartRequestParsed = (processedRequest != request);
/*
* 1 确定当前请求的处理器——handler
*
* 通过遍历handlerMappings,依次调用每个HandlerMapping的getHandler方法获取HandlerExecutionChain对象
* 只要有一个HandlerMapping的getHandler方法返回值不为null,就返回该返回值,就不会继续向后查找
* 如果在所有的handlerMapping中都没找到,则返回null。
*
* 对于RequestMappingHandlerMapping,他返回的handler就是HandlerMethod
*/
mappedHandler = getHandler(processedRequest);
//如果没有找到任何handler,那么执行noHandlerFound的逻辑
if (mappedHandler == null) {
//通常返回404响应码或者抛出NoHandlerFoundException异常
noHandlerFound(processedRequest, response);
//直接返回,doDispatch方法结束
return;
}
/*
* 2 根据handler确定当前请求的处理器适配器——HandlerAdapter
*
* 通过遍历handlerAdapters,依次调用每个HandlerAdapter的supports方法
* 只要有一个HandlerAdapter的supports方法返回true,就返回该HandlerAdapter,就不会继续向后查找
* 如果在所有的HandlerAdapter中都没找到,则直接抛出ServletException。
*
* 对于HandlerMethod,它的适配器就是RequestMappingHandlerAdapter
*/
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
/*
* 如果handler支持,则处理last-modified头
*/
String method = request.getMethod();
boolean isGet = "GET".equals(method);
//如果是GET或者HEAD请求
if (isGet || "HEAD".equals(method)) {
//获取last-modified头信息,这是对应的文档的最近一次更新时间
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
//如果没有任何改变并且是GET请求,则直接返回
//这是对于不经常改变的文档的缓存机制的支持,如果此前获取过某个文档,并且此次访问时该文档仍然没有改变,则服务器不会再次渲染该文档
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
/*
* 3 顺序应用拦截器链中的此前找到的所有拦截器的PreHandle预处理方法
* 只要有一个拦截器不通过,那么该请求就不会继续处理,而是直接返回
*/
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
/*
* 4 通过HandlerAdapter使用给定的handler实际处理请求,返回一个ModelAndView结果对象
*
* 不同的handler的实际工作流程差别非常大,其中还包括序列化、数据绑定、检验等等步骤
* 对于HandlerMethod来说,就会尝试执行对应的@ReuqestMapping方法,也就是业务逻辑
*/
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//如果存在mv并且没有view,则设置view为默认的viewName
applyDefaultViewName(processedRequest, mv);
/*
* handler正常处理完毕
* 5 倒序应用拦截器链中的此前找到的所有拦截器的postHandle后处理方法
*/
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
} catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
/*
* 6 处理执行handler返回的结果
* 主要目的是处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图,最后倒序应用拦截器的afterCompletion方法
*/
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
/*
* 倒序应用拦截器链中的此前找到的所有拦截器的afterC以上是关于Spring MVC 请求执行流程的源码深度解析两万字的主要内容,如果未能解决你的问题,请参考以下文章
Spring MVC注解Controller源码流程解析---请求匹配中的容错处理
Spring MVC注解Controller源码流程解析--HandlerAdapter执行流程--上
Spring MVC注解Controller源码流程解析--定位HandlerMethod