5.一个请求的旅行过程
5.一个请求的旅行过程
Spring 版本:5.1.14.RELEASE
该系列其他文档请查看:[《死磕 Spring MVC 源码分析 - 文章导读》][Spring MVC _ -]
在上一篇[《WebApplicationContext 容器的初始化》][WebApplicationContext]文档中分析了 Spring MVC 是如何创建两个容器的,其中创建Root WebApplicationContext 后,调用其refresh()
方法会触发刷新事件,完成 Spring IOC 初始化相关工作,会初始化各种 Spring Bean 到当前容器中,该系列文档暂不分析
我们先来了解一个请求是如何被 Spring MVC 处理的,由于整个流程涉及到的代码非常多,所以本文的重点在于解析整体的流程,主要讲解 DispatcherServlet 这个核心类,弄懂了这个流程后,才能更好的理解具体的源码,回过头再来看则会更加的豁然开朗
整体流程图
![ ][nbsp]
Spring MVC 处理请求的流程大致如上图所示
1、 用户的浏览器发送一个请求,这个请求经过互联网到达了我们的服务器Servlet容器首先接待了这个请求,并将该请求委托给DispatcherServlet
进行处理;
2、 DispatcherServlet
将该请求传给了处理器映射组件HandlerMapping
,并获取到适合该请求的HandlerExecutionChain拦截器和处理器对象;
3、 在获取到处理器后,DispatcherServlet
还不能直接调用处理器的逻辑,需要进行对处理器进行适配处理器适配成功后,DispatcherServlet
通过处理器适配器HandlerAdapter
调用处理器的逻辑,并获取返回值ModelAndView
对象;
4、 之后,DispatcherServlet
需要根据ModelAndView解析视图解析视图的工作由ViewResolver
完成,若能解析成功,ViewResolver
会返回相应的View视图对象;
5、 在获取到具体的View对象后,最后一步要做的事情就是由View渲染视图,并将渲染结果返回给用户;
以上就是 Spring MVC 处理请求的全过程,上面的流程进行了一定的简化,主要涉及到最核心的组件,还有许多其他组件没有表现出来,不过这并不影响大家对主过程的理解。
组件预览
在上一篇[《WebApplicationContext 容器的初始化》][WebApplicationContext]文档讲述 FramworkServlet 的 onRefresh 方法时,该方法由 DispatcherServlet
去实现,会初始化九大组件,如何初始化的这里暂时不展开讨论,默认会从 spring-webmvc
下面的 DispatcherServlet.properties
文件中读取组件的实现类,感兴趣可以先阅读一下源码😈,后续会依次描述
那么接下来就简单介绍一下 DispatcherServlet
和九大组件:
组件 | 说明 |
---|---|
DispatcherServlet | Spring MVC 的核心组件,是请求的入口,负责协调各个组件工作 |
MultipartResolver | 内容类型( Content-Type )为 multipart/* 的请求的解析器,例如解析处理文件上传的请求,便于获取参数信息以及上传的文件 |
HandlerMapping | 请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler )和拦截器们(interceptors ) |
HandlerAdapter | 处理器的适配器。因为处理器 handler 的类型是 Object 类型,需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处理器的实现多变,比如用户处理器可以实现 Controller 接口、HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作为一个处理器等,这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器,由它去执行处理器 |
HandlerExceptionResolver | 处理器异常解析器,将处理器( handler )执行时发生的异常,解析( 转换 )成对应的 ModelAndView 结果 |
RequestToViewNameTranslator | 视图名称转换器,用于解析出请求的默认视图名 |
LocaleResolver | 本地化(国际化)解析器,提供国际化支持 |
ThemeResolver | 主题解析器,提供可设置应用整体样式风格的支持 |
ViewResolver | 视图解析器,根据视图名和国际化,获得最终的视图 View 对象 |
FlashMapManager | FlashMap 管理器,负责重定向时,保存参数至临时存储(默认 Session) |
Spring MVC 对各个组件的职责划分的比较清晰。DispatcherServlet
负责协调,其他组件则各自做分内之事,互不干扰。经过这样的职责划分,代码会便于维护。同时对于源码阅读者来说,也会很友好。可以降低理解源码的难度,使大家能够快速理清主逻辑。这一点值得我们学习。
ThemeResolver 和 FlashMapManager 组件在该系列文档中不会进行讲解,因为几乎接触不到,感兴趣的可以去 Google 一下,嘻嘻~😅 笔者没接触过
FrameworkServlet
虽然在上面的整体流程图中,我们看到请求是直接被 DispatcherServlet 所处理,但是实际上,FrameworkServlet 才是真正的入口,再来回顾一个 DispatcherServlet 的类图,如下:
![ ][nbsp 1]
FrameworkServlet 覆盖了 HttpServlet 的以下方法:
- doGet(HttpServletRequest request, HttpServletResponse response)
- doPost(HttpServletRequest request, HttpServletResponse response)
- doPut(HttpServletRequest request, HttpServletResponse response)
- doDelete(HttpServletRequest request, HttpServletResponse response)
- doOptions(HttpServletRequest request, HttpServletResponse response)
- doTrace(HttpServletRequest request, HttpServletResponse response)
- service(HttpServletRequest request, HttpServletResponse response)
这些方法分别处理不同 HTTP 请求类型的请求,最终都会调用另一个 processRequest(HttpServletRequest request, HttpServletResponse response)
方法
其中doGet
、doPost
、doPut
和doDelete
四个方法是直接调用 processRequest
方法的
service
service(HttpServletRequest request, HttpServletResponse response)
方法,用于处理请求,方法如下:
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// <1> 获得请求方法
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
// <2> 处理 PATCH 请求
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
// <3> 处理其他类型的请求
else {
super.service(request, response);
}
}
1、 获得HttpMethod请求方法;
2、 若请求方法是PATCH
,调用processRequest(HttpServletRequestrequest,HttpServletResponseresponse)
方法,处理请求;
因为 HttpServlet 默认没提供处理 Patch
请求类型的请求 ,所以只能通过覆盖父类的 service
方法来实现
3、 如果是其他类型的请求,则直接调用父类的service
方法,该方法如下:;
// HttpServlet.java
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
// Note that this means NO servlet supports whatever method was requested, anywhere on this server.
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
可能你会有疑惑,为什么不在 service(HttpServletRequest request, HttpServletResponse response)
方法,直接调用 processRequest(HttpServletRequest request, HttpServletResponse response)
方法就好了?因为针对不同的请求方法,处理略微有所不同,父类 HttpServlet 已经处理了。
doOptions
doOptions(HttpServletRequest request, HttpServletResponse response)
方法,用于处理 OPTIONS 类型的请求,方法如下:
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 如果 dispatchOptionsRequest 为 true ,则处理该请求,默认为 true
if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {
// 处理请求
processRequest(request, response);
// 如果响应 Header 包含 "Allow" ,则不需要交给父方法处理
if (response.containsHeader("Allow")) {
// Proper OPTIONS response coming from a handler - we're done.
return;
}
}
// Use response wrapper in order to always add PATCH to the allowed methods
// 调用父方法,并在响应 Header 的 "Allow" 增加 PATCH 的值
super.doOptions(request, new HttpServletResponseWrapper(response) {
@Override
public void setHeader(String name, String value) {
if ("Allow".equals(name)) {
value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();
}
super.setHeader(name, value);
}
});
}
使用场景:AJAX 进行跨域请求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全
doTrace
doTrace(HttpServletRequest request, HttpServletResponse response)
方法,用于处理 TRACE 类型的请求,方法如下:
protected void doTrace(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 如果 dispatchTraceRequest 为 true ,则处理该请求,默认为 false
if (this.dispatchTraceRequest) {
// 处理请求
processRequest(request, response);
// 如果响应的内容类型为 "message/http" ,则不需要交给父方法处理
if ("message/http".equals(response.getContentType())) {
// Proper TRACE response coming from a handler - we're done.
return;
}
}
// 调用父方法
super.doTrace(request, response);
}
回显服务器收到的请求,主要用于测试或诊断,笔者目前还没接触过😈
processRequest
processRequest(HttpServletRequest request, HttpServletResponse response)
方法,用于处理请求,方法如下:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// <1> 记录当前时间,用于计算处理请求花费的时间
long startTime = System.currentTimeMillis();
// <2> 记录异常,用于保存处理请求过程中发送的异常
Throwable failureCause = null;
// <3> TODO
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
// <4> TODO
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
// <5> TODO
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// <6> TODO
initContextHolders(request, localeContext, requestAttributes);
try {
// <7> 执行真正的逻辑
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex; // <8> 记录抛出的异常
throw ex;
}
catch (Throwable ex) {
failureCause = ex; // <8> 记录抛出的异常
throw new NestedServletException("Request processing failed", ex);
}
finally {
// <9> TODO
resetContextHolders(request, previousLocaleContext, previousAttributes);
// <10> TODO
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
// <11> 如果日志级别为 DEBUG,则打印请求日志
logResult(request, response, failureCause, asyncManager);
// <12> 发布 ServletRequestHandledEvent 请求处理完成事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
<1>
记录当前时间,用于计算处理请求花费的时间
<2>
记录异常,用于保存处理请求过程中发送的异常
<7>
【核心】调用 doService(HttpServletRequest request, HttpServletResponse response)
抽象方法,执行真正的逻辑,由 DispatcherServlet
实现,所以这就是 DispatcherServlet 处理请求的真正入口
<8>
记录执行过程抛出的异常,最终在 finally
的代码段中使用。
<11>
如果日志级别为 DEBUG,则打印请求日志
<12>
调用 publishRequestHandledEvent
方法,通过 WebApplicationContext 发布 ServletRequestHandledEvent 请求处理完成事件,目前好像 Spring MVC 没有监听这个事件,可以自己写一个监听器用于获取请求信息,示例如下:
@Component
@Log4j2
public class ServletRequestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent>{
@Override
public void onApplicationEvent(ServletRequestHandledEvent event) {
log.info("请求描述:{}", event.getDescription());
log.info("请求路径:{}", event.getRequestUrl());
log.info("开始时间:{}", event.getTimestamp());
log.info("请求耗时:{}", event.getProcessingTimeMillis());
log.info("状 态 码:{}", event.getStatusCode());
log.info("失败原因:{}", event.getFailureCause());
}
}
到这里,FrameworkServlet 算是讲完了,接下来就要开始讲 DispatcherServlet 这个核心类了
DispatcherServlet
org.springframework.web.servlet.DispatcherServlet
核心类,作为 Spring MVC 的核心类,承担调度器的角色,协调各个组件进行工作,处理请求,一起来揭开这神秘的面纱吧😈
静态代码块
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
会从DispatcherServlet.properties
文件中加载默认的组件实现类,将相关配置加载到 defaultStrategies
中,文件如下:
### org.springframework.web.servlet.DispatcherServlet.properties
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
可以看到各个组件的默认实现类
构造方法
/** MultipartResolver used by this servlet. multipart 数据(文件)处理器 */
@Nullable
private MultipartResolver multipartResolver;
/** LocaleResolver used by this servlet. 语言处理器,提供国际化的支持 */
@Nullable
private LocaleResolver localeResolver;
/** ThemeResolver used by this servlet. 主题处理器,设置需要应用的整体样式 */
@Nullable
private ThemeResolver themeResolver;
/** List of HandlerMappings used by this servlet. 处理器匹配器,返回请求对应的处理器和拦截器们 */
@Nullable
private List<HandlerMapping> handlerMappings;
/** List of HandlerAdapters used by this servlet. 处理器适配器,用于执行处理器 */
@Nullable
private List<HandlerAdapter> handlerAdapters;
/** List of HandlerExceptionResolvers used by this servlet. 异常处理器,用于解析处理器发生的异常 */
@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;
/** RequestToViewNameTranslator used by this servlet. 视图名称转换器 */
@Nullable
private RequestToViewNameTranslator viewNameTranslator;
/** FlashMapManager used by this servlet. FlashMap 管理器,负责重定向时保存参数到临时存储(默认 Session)中 */
@Nullable
private FlashMapManager flashMapManager;
/** List of ViewResolvers used by this servlet. 视图解析器,根据视图名称和语言,获取 View 视图 */
@Nullable
private List<ViewResolver> viewResolvers;
public DispatcherServlet() {
super();
setDispatchOptionsRequest(true);
}
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
setDispatchOptionsRequest(true);
}
定义了九个组件,在组件预览中已经做过简单介绍了
构造方法中都会设置 dispatchOptionsRequest
为 true
,在父类 FrameworkServlet 中可以看到,如果请求是 OPTIONS
则会处理请求
onRefresh
onRefresh(ApplicationContext context)
方法,初始化 Spring MVC 的各个组件,方法如下:
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
// 初始化 MultipartResolver
initMultipartResolver(context);
// 初始化 LocaleResolver
initLocaleResolver(context);
// 初始化 ThemeResolver
initThemeResolver(context);
// 初始化 HandlerMapping
initHandlerMappings(context);
// 初始化 HandlerAdapter
initHandlerAdapters(context);
// 初始化 HandlerExceptionResolver
initHandlerExceptionResolvers(context);
// 初始化 RequestToViewNameTranslator
initRequestToViewNameTranslator(context);
// 初始化 ViewResolver
initViewResolvers(context);
// 初始化 FlashMapManager
initFlashMapManager(context);
}
创建 Servlet WebApplicationContext 容器后会触发该方法,在[《WebApplicationContext 容器的初始化》][WebApplicationContext]的 FrameworkServlet小节的 onRefresh 方法中提到过
可以看到每个方法会初始化构造方法中的每个组件
1. doService
doService(HttpServletRequest request, HttpServletResponse response)
方法,DispatcherServlet 的处理请求的入口方法,代码如下:
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// <1> 如果日志级别为 DEBUG,则打印请求日志
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
// <2> 保存当前请求中相关属性的一个快照
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.
// <3> 设置 Spring 框架中的常用对象到 request 属性中
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());
// <4> FlashMap 的相关配置
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 {
// <5> 执行请求的分发
doDispatch(request, response);
}
finally {
// <6> 异步处理相关
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
1、 调用logRequest(HttpServletRequestrequest)
方法,如果日志级别为DEBUG,则打印请求日志;
2、 保存当前请求中相关属性的一个快照,作为异步处理的属性值,防止被修改,暂时忽略;
3、 设置Spring框架中的常用对象到request属性中,例如webApplicationContext
、localeResolver
、themeResolver
;
4、 FlashMap的相关配置,暂时忽略;
5、 **【重点】**调用doDispatch(HttpServletRequestrequest,HttpServletResponseresponse)
方法,执行请求的分发;
6、 异步处理相关,暂时忽略;
2. doDispatch【核心】
doDispatch(HttpServletRequest request, HttpServletResponse response)
方法,行请求的分发,在开始看具体的代码实现之前,我们在来回味下这张图片:
![ ][nbsp 2]
这张图,更多的反应的是 DispatcherServlet 的 doDispatch(...)
方法的核心流程,方法如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
// <1> 获取异步管理器
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// <2> 检测请求是否为上传请求,如果是则通过 multipartResolver 将其封装成 MultipartHttpServletRequest 对象
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// <3> 获得请求对应的 HandlerExecutionChain 对象(HandlerMethod 和 HandlerInterceptor 拦截器们)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) { // <3.1> 如果获取不到,则根据配置抛出异常或返回 404 错误
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// <4> 获得当前 handler 对应的 HandlerAdapter 对象
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// <4.1> 处理有Last-Modified请求头的场景
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) { // 不清楚为什么要判断方法类型为'HEAD'
// 获取请求中服务器端最后被修改时间
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// <5> 前置处理 拦截器
// 注意:该方法如果有一个拦截器的前置处理返回false,则开始倒序触发所有的拦截器的 已完成处理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// <6> 真正的调用 handler 方法,也就是执行对应的方法,并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// <7> 如果是异步
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// <8> 无视图的情况下设置默认视图名称
applyDefaultViewName(processedRequest, mv);
// <9> 后置处理 拦截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex; // <10> 记录异常
}
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);
}
// <11> 处理正常和异常的请求调用结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// <12> 已完成处理 拦截器
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
// <12> 已完成处理 拦截器
triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));
}
finally {
// <13.1> Asyn
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
// <13.1> 如果是上传请求则清理资源
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
1、 获得WebAsyncManager异步处理器,暂时忽略;
2、 **【文件】**调用checkMultipart(HttpServletRequestrequest)
方法,检测请求是否为上传请求,如果是则通过multipartResolver
组件将其封装成MultipartHttpServletRequest对象,便于获取参数信息以及文件;
3、 **【处理器匹配器】**调用getHandler(HttpServletRequestrequest)
方法,通过HandlerMapping组件获得请求对应的HandlerExecutionChain
处理器执行链,包含HandlerMethod
处理器和HandlerInterceptor
拦截器们;
1、 如果获取不到对应的执行链,则根据配置抛出异常或返回404错误;
4、 **【处理器适配器】**调用getHandlerAdapter(Objecthandler)
方法,获得当前处理器对应的HandlerAdapter适配器对象;
5、 处理有Last-Modified请求头的场景,暂时忽略;
6、 【拦截器】调用HandlerExecutionChain
执行链的applyPreHandle(HttpServletRequestrequest,HttpServletResponseresponse)
方法,拦截器的前置处理;
如果出现拦截器前置处理失败,则会调用拦截器的已完成处理方法(倒序)
7、 【重点】调用HandlerAdapter
适配器的handle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)
方法,真正的执行处理器,也就是执行对应的方法(例如我们定义的Controller中的方法),并返回视图;
8、 如果是异步,则直接return
,注意,还是会执行finally
中的代码;
9、 调用applyDefaultViewName(HttpServletRequestrequest,ModelAndViewmv)
方法,ModelAndView不为空,但是没有视图,则设置默认视图名称,使用到了viewNameTranslator
视图名称转换器组件;
10、 【拦截器】调用HandlerExecutionChain
执行链的applyPostHandle(HttpServletRequestrequest,HttpServletResponseresponse,ModelAndViewmv)
方法,拦截器的后置处理(倒序);
11、 记录异常,注意,此处仅仅记录,不会抛出异常,而是统一交给<11>
处理;
12、 【处理执行结果】调用processDispatchResult(HttpServletRequestrequest,HttpServletResponseresponse,HandlerExecutionChainmappedHandler,ModelAndViewmv,Exceptionexception)
方法,处理正常和异常的请求调用结果,包含页面渲染;
13、 【拦截器】如果上一步发生了异常,则调用triggerAfterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,HandlerExecutionChainmappedHandler,Exceptionex)
方法,即调用HandlerInterceptor
执行链的triggerAfterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Exceptionex)
方法,拦截器已完成处理(倒序);
14、 finally
代码块,异步处理,暂时忽略,如果是涉及到文件的请求,则清理相关资源;
上面将DispatcherServlet 处理请求的整个流程步骤都列出来了,涉及到的组件分别在后续的文档中将分开进行分析
2.1 checkMultipart
checkMultipart(HttpServletRequest request)
方法,检测请求是否为上传请求,如果是则通过 multipartResolver
组件将其封装成 MultipartHttpServletRequest 对象
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
// 如果该请求是一个涉及到 multipart (文件)的请求
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
}
}
else if (hasMultipartException(request)) {
logger.debug("Multipart resolution previously failed for current request - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
try {
// 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象,解析请求里面的参数以及文件
return this.multipartResolver.resolveMultipart(request);
}
catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
}
else {
throw ex;
}
}
}
}
// If not returned before: return original request.
return request;
}
2.2 getHandler
getHandler(HttpServletRequest request)
方法,通过 HandlerMapping 组件获得请求对应的 HandlerExecutionChain
处理器执行链,包含 HandlerMethod
处理器和 HandlerInterceptor
拦截器们
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
2.3 getHandlerAdapter
getHandlerAdapter(Object handler)
方法,获得当前处理器对应的 HandlerAdapter 适配器对象
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
2.4 applyDefaultViewName
applyDefaultViewName(HttpServletRequest request, ModelAndView mv)
方法,ModelAndView 不为空,但是没有视图,则设置默认视图名称
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
if (mv != null && !mv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
mv.setViewName(defaultViewName);
}
}
}
@Nullable
protected String getDefaultViewName(HttpServletRequest request) throws Exception {
// 使用到了 viewNameTranslator 视图名称转换器组件
return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
}
2.5 processDispatchResult
processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)
方法,处理正常和异常的请求调用结果,方法如下:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// <1> 标记是否为处理生成异常的 ModelAndView 对象
boolean errorView = false;
// <2> 如果该请求出现异常
if (exception != null) {
// 情况一,从 ModelAndViewDefiningException 中获得 ModelAndView 对象
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
// 情况二,处理异常,生成 ModelAndView 对象
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
// 标记 errorView
errorView = (mv != null);
}
}
// Did the handler return a view to render?
// <3> 是否进行页面渲染
if (mv != null && !mv.wasCleared()) {
// <3.1> 渲染页面
render(mv, request, response);
// <3.2> 清理请求中的错误消息属性
// 因为上述的情况二中 processHandlerException 会通过 WebUtils 设置错误消息属性,所以这里得清理一下
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
// <4> 如果是异步
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
// <5> 已完成处理 拦截器
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
1、 标记是否为处理生成异常的ModelAndView对象;
2、 如果该请求出现异常;
1、 情况一,从ModelAndViewDefiningException中获得ModelAndView对象;
2、 情况二,调用processHandlerException(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)
方法,处理异常,生成ModelAndView对象;
3、 如果ModelAndView不为空且没有被清理,例如你现在使用最多的@ResponseBody这里就为空,不需要渲染;
1、 调用render(ModelAndViewmv,HttpServletRequestrequest,HttpServletResponseresponse)
方法,渲染页面;
2、 如果是上面第2
步中情况二生成的ModelAndView对象,则需要清理请求中的错误消息属性,因为上述的情况二会通过WebUtils设置错误消息属性,所以这里得清理一下;
4、 如果是异步请求,则直接return
;
5、 正常情况下,调用triggerAfterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,HandlerExecutionChainmappedHandler,Exceptionex)
方法,即调用HandlerInterceptor
执行链的triggerAfterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Exceptionex)
方法,拦截器已完成处理(倒序);
2.5.1 processHandlerException
processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,处理异常,生成 ModelAndView 对象
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
// 移除 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
// <a> 遍历 HandlerExceptionResolver 数组,解析异常,生成 ModelAndView 对象
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
// 遍历 HandlerExceptionResolver 数组
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
// 解析异常,生成 ModelAndView 对象
exMv = resolver.resolveException(request, response, handler, ex);
// 生成成功,结束循环
if (exMv != null) {
break;
}
}
}
// <b> 情况一,生成了 ModelAndView 对象,进行返回
if (exMv != null) {
// ModelAndView 对象为空,则返回 null
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
// 没有视图则设置默认视图
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
// 打印日志
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
// 设置请求中的错误消息属性
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
// <c> 情况二,未生成 ModelAndView 对象,则抛出异常
throw ex;
}
<a>
处,遍历 HandlerExceptionResolver 数组,调用 HandlerExceptionResolver#resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,解析异常,生成 ModelAndView 对象
<b>
处,情况一,生成了 ModelAndView 对象,逻辑比较简单
<c>
处,情况二,未生成 ModelAndView 对象,则抛出异常
2.5.2 render
render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
方法,渲染 ModelAndView,方法如下:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
// <1> 解析 request 中获得 Locale 对象,并设置到 response 中
Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
// 获得 View 对象
View view;
String viewName = mv.getViewName();
// 情况一,使用 viewName 获得 View 对象
if (viewName != null) {
// We need to resolve the view name.
// <2.1> 使用 viewName 获得 View 对象
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) { // 获取不到,抛出 ServletException 异常
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
// 情况二,直接使用 ModelAndView 对象的 View 对象
else {
// No need to lookup: the ModelAndView object contains the actual View object.
// <2.2> 直接使用 ModelAndView 对象的 View 对象
view = mv.getView();
if (view == null) { // 获取不到,抛出 ServletException 异常
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
// 打印日志
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
// <3> 设置响应的状态码
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// <4> 渲染页面
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
1、 调用LocaleResolver
的resolveLocale(HttpServletRequestrequest)
方法,从request
中获得Locale对象,并设置到response
中;
2、 获得View对象,有两种情况;
1、 调用resolveViewName
方法,使用viewName
通过获得View对象,方法如下:;
```java
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
// 遍历 ViewResolver 数组
for (ViewResolver viewResolver : this.viewResolvers) {
// 根据 viewName + locale 参数,解析出 View 对象
View view = viewResolver.resolveViewName(viewName, locale);
// 解析成功,直接返回 View 对象
if (view != null) {
return view;
}
}
}
return null;
}
**2、** 直接使用ModelAndView对象的View对象;
**3、** 设置响应的状态码;
**4、** 调用`View`的`render(Map<String,?>model,HttpServletRequestrequest,HttpServletResponseresponse)`方法,渲染视图;
### 总结 ###
本文对Spring MVC 处理请求的整个过程进行了分析,核心就是通过 `DispatcherServlet` 协调各个组件工作,处理请求,因为 `DispatcherServlet` 是一个 Servlet,在 Servlet 容器中,会将请求交由它来处理。
通过本文对 `DispatcherServlet` 是如何处理请求已经有了一个整体的认识,不过在整个处理过程中涉及到的各个 Spring MVC 组件还没有进行分析,对于许多细节存在疑惑,不要慌,那么接下来会对每一个 Spring MVC 组件进行分析。这样,便于我们对 Spring MVC 的理解,然后再回过头来思考 `DispatcherServlet` 这个类,能够更好的将这些组件串联在一起。先整体,后局部,逐步逐步抽丝剥茧,看清理透。
--------------------
>流程示意图,来自 [SpringMVC - 运行流程图及原理分析][SpringMVC -]
>
>![ ][nbsp 3]
>
>代码序列图
>
>![ ][nbsp 4]
>
>--------------------
>
>流程示意图,来自《看透 Spring MVC 源代码分析与实践》 书籍中的第 123 页
>
>![ ][nbsp 5]
>参考文章:**芋道源码**[《死磕 Spring MVC 源码分析》][Spring MVC]
>
>参考文章:[Spring 揭秘 -- 寻找遗失的 web.xml][Spring _ -- _ web.xml]
版权声明:本文不是「本站」原创文章,版权归原作者所有 | [原文地址:][Link 2]
[Link 1]: https://wx.zsxq.com/
[Spring MVC _ GitHub]: https://github.com/liu844869663/spring-framework
[Spring MVC _ -]: https://www.cnblogs.com/lifullmoon/p/14123963.html
[WebApplicationContext]: https://www.cnblogs.com/lifullmoon/p/14131802.html
[nbsp]: https://web-1259428203.cos.ap-chengdu.myqcloud.com/1746848971244.png
[nbsp 1]: https://web-1259428203.cos.ap-chengdu.myqcloud.com/1746848973219.png
[nbsp 2]: https://web-1259428203.cos.ap-chengdu.myqcloud.com/1746848973402.png
[SpringMVC -]: https://blog.csdn.net/J080624/article/details/77990164
[nbsp 3]: https://web-1259428203.cos.ap-chengdu.myqcloud.com/1746848973676.png
[nbsp 4]: https://web-1259428203.cos.ap-chengdu.myqcloud.com/1746848975543.png
[nbsp 5]: https://web-1259428203.cos.ap-chengdu.myqcloud.com/1746848977183.png
[Spring MVC]: http://svip.iocoder.cn/categories/Spring-MVC/
[Spring _ -- _ web.xml]: https://www.cnkirito.moe/servlet-explore/
[Link 2]: https://www.cnblogs.com/lifullmoon/p/14131862.html