1. 概述
本文,我们来一览一个用户的请求,是如何被 DispatcherServlet 处理的。如下图所示:
整体流程实际不复杂,但是涉及的全部代码会非常多,所以本文重点在于解析整体的流程。特别具体和细节的代码实现,我们会放到后续的文章,一篇一篇细细咀嚼。
1.1 如何调试
艿艿:自我吐槽,写完之后才发现,忘记提供测试示例了。
比较简单,调试 org.springframework.web.servlet.DispatcherServletTests
这个单元测试类,可以运行各种单元测试方法,执行各种情况。😈
2. FrameworkServlet
虽然在 「1. 概述」 的整体流程图,我们看到请求首先是被 DispatcherServlet 所处理,但是实际上,FrameworkServlet 才是真正的入门。FrameworkServlet 会实现
#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)
等方法。而这些实现,最终会调用 #processRequest(HttpServletRequest request, HttpServletResponse response)
方法,处理请求。
2.1 不同 HttpMethod 的请求处理
2.1.1 service
#service(HttpServletRequest request, HttpServletResponse response)
方法,代码如下:
// FrameworkServlet.java |
<1>
处,获得请求方法。<2.1>
处,若请求方法是HttpMethod.PATCH
,调用#processRequest(HttpServletRequest request, HttpServletResponse response)
方法,处理请求。因为 HttpServlet 默认没提供#doPatch(HttpServletRequest request, HttpServletResponse response)
方法,所以只能通过父类的#service(...)
方法,从而实现。另外,关于 processRequest 的详细解析,见 「2.2 processRequest」 。<2.2>
处,其它类型的请求方法,还是调用父类的#service(HttpServletRequest request, HttpServletResponse response)
方法,进行处理。代码如下:// 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)
方法就好列?因为针对不同的请求方法,处理略微有所不同。
2.1.2 doGet & doPost & doPut & doDelete
这四个方法,都是直接调用 #processRequest(HttpServletRequest request, HttpServletResponse response)
方法,处理请求。代码如下:
// FrameworkServlet.java |
2.1.3 doOptions
// FrameworkServlet.java |
- 选读,因为 OPTIONS 请求方法,实际场景下用的少。
- 可参考 《HTTP 的请求方法 OPTIONS》 。
2.1.4 doTrace
// FrameworkServlet.java |
- 选读,因为 TRACE 请求方法,实际场景下用的少。
- 可参读 《HTTP Method详细解读(
GET
HEAD
POST
OPTIONS
PUT
DELETE
TRACE
CONNECT
)》 的 「9.8 TRACE」 小节。
2.2 processRequest
#processRequest(HttpServletRequest request, HttpServletResponse response)
方法,处理请求。代码如下:
// FrameworkServlet.java |
<1>
处,记录当前时间,用于计算 web 请求的处理时间。<2>
处,记录异常。<3>
处,TODO 1001 Locale<4>
处,TODO 1002 RequestAttributes<5>
处,TODO 1003 Asyn<6>
处,TODO 1001 + 1002【重要】
<7>
处,调用#doService(HttpServletRequest request, HttpServletResponse response)
抽象方法,执行真正的逻辑。代码如下:// FrameworkServlet.java
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;- 该抽象方法由 DispatcherServlet 实现,所以这就是 DispatcherServlet 处理请求的真正入口。详细解析,见 「3. DispatcherServlet」 。
<8>
处,记录抛出的异常,最终在finally
的代码段中使用。<9>
处,TODO 1001 + 1002<10>
处,TODO 1001 + 1002<11>
处,调用#logResult(HttpServletRequest request, HttpServletResponse response, Throwable failureCause, WebAsyncManager asyncManager)
方法,打印请求日志,并且日志级别为 DEBUG 。这个方法,感兴趣的胖友,点击 传送门 查看。<12>
处,调用#publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response, long startTime, Throwable failureCause)
方法,发布org.springframework.web.context.support.ServletRequestHandledEvent
事件。代码如下:// FrameworkServlet.java
/** Should we publish a ServletRequestHandledEvent at the end of each request?. */
private boolean publishEvents = true;
private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
long startTime, @Nullable Throwable failureCause) {
// 如果开启发布事件
if (this.publishEvents && this.webApplicationContext != null) {
// Whether or not we succeeded, publish an event.
long processingTime = System.currentTimeMillis() - startTime;
// 创建 ServletRequestHandledEvent 事件,并进行发布
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause, response.getStatus()));
}
}- 关于 ServletRequestHandledEvent 的监听使用,可参考 《使用 Spring ApplicationListener 容器监听器来记录请求信息》 。
艿艿:好像到了此处,一直没写到 DispatcherServlet 。😈 有种在啰嗦的感觉,嘿嘿。
3. DispatcherServlet
3.1 doService
#doService(HttpServletRequest request, HttpServletResponse response)
方法,DispatcherServlet 的处理请求的入口方法,代码如下:
// DispatcherServlet.java |
<1>
处,调用#logRequest(HttpServletRequest request)
方法,打印请求日志,并且日志级别为 DEBUG 。这个方法,感兴趣的胖友,点击 传送门 查看。<2>
处,TODO 1003 芋艿<3>
处,设置 Spring 框架中的常用对象到request
的属性中。<4>
处,TODO 1004 芋艿 flashMapManager<5>
处,调用#doDispatch(HttpServletRequest request, HttpServletResponse response)
方法,执行请求的分发。详细解析,见 「3.2 doDispatch」 。<6>
处,TODO 1003 芋艿
3.2 doDispatch
#doDispatch(HttpServletRequest request, HttpServletResponse response)
方法,执行请求的分发。在开始看具体的代码实现之前,我们在来回味下这张图片:
- 实际上,这张图,更多的反应的是 DispatcherServlet 的
#DispatcherServlet(...)
方法的核心流程。
代码如下:
// DispatcherServlet.java |
<1>
处,TODO 1003 Async<2>
处,调用#checkMultipart(HttpServletRequest request)
方法,检查是否是上传请求。如果是,则封装成 MultipartHttpServletRequest 对象。详细解析,见 《精尽 Spring MVC 源码解析 —— MultipartResolver》 中。<3>
处,调用#getHandler(HttpServletRequest request)
方法,返回请求对应的是 HandlerExecutionChain 对象,它包含处理器(handler
)和拦截器们( HandlerInterceptor 数组 )。代码如下:// DispatcherServlet.java
/** List of HandlerMappings used by this servlet. */
private List<HandlerMapping> handlerMappings;
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
// 遍历 HandlerMapping 数组
for (HandlerMapping mapping : this.handlerMappings) {
// 获得请求对应的 HandlerExecutionChain 对象
HandlerExecutionChain handler = mapping.getHandler(request);
// 获得到,则返回
if (handler != null) {
return handler;
}
}
}
return null;
}<3.1>
处,如果获取不到,则调用#noHandlerFound(HttpServletRequest request, HttpServletResponse response)
根据配置抛出异常或返回 404 错误。 代码比较简单,胖友点击 传送门 自己看该方法。<4>
处,调用#getHandlerAdapter(Object handler)
方法,获得当前handler
对应的 HandlerAdapter 对象。代码如下:// DispatcherServlet.java
/** List of HandlerAdapters used by this servlet. */
private List<HandlerAdapter> handlerAdapters;
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
// 遍历 HandlerAdapter 数组
for (HandlerAdapter adapter : this.handlerAdapters) {
// 判断是否支持当前处理器
if (adapter.supports(handler)) {
// 如果支持,则返回
return adapter;
}
}
}
// 没找到对应的 HandlerAdapter 对象,抛出 ServletException 异常
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}<4.1>
处,见 《精尽 Spring MVC 源码解析 —— HandlerAdapter 组件(一)之 HandlerAdapter》- 【拦截器】
<5>
处,调用HandlerExecutionChain#applyPreHandle(HttpServletRequest request, HttpServletResponse response)
方法,拦截器的前置处理,即调用HandlerInterceptor#preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
方法。详细解析,见 《精尽 Spring MVC 源码分析 —— HandlerMapping 组件(二)之 HandlerInterceptor》 。 - 【Controller】
<6>
处,调用HandlerAdapter#handle(HttpServletRequest request, HttpServletResponse response, Object handler)
方法,真正的调用handler
方法,并返回视图。这里,一般就会调用我们定义的 Controller 的方法。详细解析,见 TODO 。 <7>
处,TODO 1003 Asyn<8>
处,调用#applyDefaultViewName(HttpServletRequest request, ModelAndView mv)
方法,当无视图的情况下,设置默认视图。代码如下:// DispatcherServlet.java
/** RequestToViewNameTranslator used by this servlet. */
private RequestToViewNameTranslator viewNameTranslator;
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);
}
}
}
protected String getDefaultViewName(HttpServletRequest request) throws Exception {
// 从请求中,获得视图
return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
}【拦截器】
<9>
处,调用HandlerExecutionChain#applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv)
方法,拦截器的后置处理,即调用HandlerInterceptor#postHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
方法。详细解析,见 《精尽 Spring MVC 源码分析 —— HandlerMapping 组件(二)之 HandlerInterceptor》 。<10>
处,记录异常。注意,此处仅仅记录,不会抛出异常,而是统一交给<11>
处理。<11>
处,调用#processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)
方法,处理正常和异常的请求调用结果。注意,正常的、异常的,都会进行处理。详细解析,见 「3.3 processDispatchResult」 。- 【拦截器】
<12>
处,调用#triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, Exception ex)
方法,拦截器的已完成处理,即调用HandlerInterceptor#triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
方法。详细解析,见 《精尽 Spring MVC 源码分析 —— HandlerMapping 组件(二)之 HandlerInterceptor》 。 <13.1>
处,TODO 1003 Asyn<13.2>
处,如果是上传请求,则调用#cleanupMultipart(HttpServletRequest request)
方法,清理资源。详细解析,见 《精尽 Spring MVC 源码解析 —— MultipartResolver》 中。
3.3 processDispatchResult
#processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)
方法,处理正常和异常的请求调用结果。代码如下:
// DispatcherServlet.java |
<1>
处,errorView
属性,标记是否是生成的 ModelAndView 对象。<2>
处,如果是否异常的结果。<2.1>
处,情况一,从 ModelAndViewDefiningException 中获得 ModelAndView 对象。<2.2>
处,情况二,调用#processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,处理异常,生成 ModelAndView 对象。代码如下:// DispatcherServlet.java
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;
}
}
}
// 情况一,生成了 ModelAndView 对象,进行返回
if (exMv != null) {
// ModelAndView 对象为空,则返回 null
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex); // 记录异常到 request 中
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;
}
// 情况二,未生成 ModelAndView 对象,则抛出异常
throw ex;
}<a>
处,遍历 HandlerExceptionResolver 数组,调用HandlerExceptionResolver#resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,解析异常,生成 ModelAndView 对象。详细解析,TODO<b>
处,情况一,生成了 ModelAndView 对象,进行返回。当然,这里的后续代码还有 10 多行,比较简单,胖友自己瞅瞅就 OK 啦。<c>
处,情况二,未生成 ModelAndView 对象,则抛出异常。
<3.1>
处,调用#render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
方法,渲染页面。详细解析,见 「3.4 render」 。<3.2>
处,当是<2>
处的情况二时,则调用WebUtils#clearErrorRequestAttributes(HttpServletRequest request)
方法,清理请求中的错误消息属性。为什么会有这一步呢?答案在#processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法中,会调用WebUtils#exposeErrorRequestAttributes(HttpServletRequest request, Throwable ex, String servletName)
方法,设置请求中的错误消息属性。<4>
处,TODO 1003 芋艿- 【拦截器】
<5>
处,调用#triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, Exception ex)
方法,拦截器的已完成处理,即调用HandlerInterceptor#triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
方法。详细解析,见 《精尽 Spring MVC 源码分析 —— HandlerMapping 组件(二)之 HandlerInterceptor》 。
3.4 render
#render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
方法,渲染 ModelAndView 。代码如下:
// DispatcherServlet.java |
<1>
处,调用LocaleResolver#resolveLocale(HttpServletRequest request)
方法,从request
中获得 Locale 对象,并设置到response
中。详细解析,见 TODO 1001<2>
处,获得 View 对象。分成两种情况,代码比较简单,胖友自己瞅瞅。<2.1>
处,调用#resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request)
方法,使用viewName
获得 View 对象。代码如下:// DispatcherServlet.java
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;
}<3>
处,设置响应的状态码。<4>
处,调用View#render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
方法,渲染视图。详细解析,见 《精尽 Spring MVC 源码解析 —— ViewResolver》 。
666. 彩蛋
到此,我们已经对 DispatcherServlet 是如何处理请求已经有了整体的认识。当然,我们对每个 Spring MVC 组件,细节暂时没有进行深扣,正如我们在看本文,会有大量的 TODO 。不要方,后面的每一篇,我们会对每个 Spring MVC 组件逐个解析。这样,我们对 Spring MVC 会更加了解。
其实,这也是看源码的套路,先整理,后局部,逐步逐步抽丝剥茧,看清理透。
还有,在看完后续的 Spring MVC 的每个组件后,胖友可以在回过头在重新看看这个文章,是否能够将文章更好的串联在一起。
下面,艿艿整理了一些网络上讲述 Spring MVC 处理请求的一些图,帮助我们更好的理解这个过程。
流程示意图:
代码序列图:
FROM 《看透 Spring MVC:源代码分析与实践》 P123
流程示意图:
参考和推荐如下文章: