SpringMVC 篇(二)DispatcherServlet
引言
前一篇文章 【SpringMVC 篇(一)DispatcherServlet 初始化】 中介绍了刷新的触发、基础组建的加载以及以 HandlerMapping 为例讲解了一下 url 与 controller 的映射。那么这篇文章将继续介绍 DispatcherServlet 关于请求处理的部分。
请求
在开始介绍 DispatcherServlet 处理请求之前,我们回忆一下原始的 servlet 是如何处理请求的。
Servlet
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
servlet 是一个接口,该接口中的 service 方法可以说是核心方法,所有的请求都会由该方法进行调度。根据请求的方法来引导你到 doXXX 系列的方法。而 doXXX 系列的方法并没有在该接口中进行声明,而是放到了它的子类 HttpServlet 中。而 DispatcherServlet 也是一个 servlet,所以它也按照该流程处理请求。
时序图
image.png我们先看一下请求是如何通过 HttpServlet 以及 FrameworkServlet 中转到 DispatcherServlet 的。
先对请求进行分析:
- 请求进入 HttpServlet 的 service(ServletRequest, ServletResponse) 方法,该方法将 ServletRequest 、ServletResponse 转化为 HttpServletRequest、HttpServletResponse。
- HttpServlet 调用 FrameworkServlet 的 service(ServletRequest, ServletResponse) 方法。该方法对请求的 Http 方法进行判断,然后调用 HttpServlet 的 service(ServletRequest, ServletResponse) 方法。
- HttpServlet 的 service(ServletRequest, ServletResponse) 方法根据请求调用 FrameworkServlet 的 doXXX 系列方法。
- FrameworkServlet 的 doXXX 方法调用 processRequest 方法,该方法将请求转到 DispatcherServlet 的 doService() 方法。
- DispatcherServlet 的 doService() 方法调用 doDispatcher() 方法。
过程比较绕,但是最终调用的其实就是 DispatcherServlet 的 doDispatch() 方法了,不过在介绍它之前,我想有必要从 FrameworkServlet 的 processRequest() 方法开始。看它有没有对请求对象做处理。
FrameworkServlet
DispatcherServlet 的直接父类。
FrameworkServlet
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// 从当前线程或其子线程中获取原始本地化上下文对象
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 从请求对象中获取本地化信息,然后构建出本地化上下文对象
LocaleContext localeContext = buildLocaleContext(request);
// 从当前线程及其子线程中获取原始 RequestAttributes 对象
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 根据当前请求、响应对象以及原始 previousAttributes 创建 requestAttributes 对象
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
// 创建异步管理器,并与当前请求对象关联(设置到 request 属性表中)
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
// 为异步管理器注册拦截器(设置 ContextHolder 对象,以及请求之后将其清空)
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 分别初始化 localeContextHolder 以及 requestContextHolder 对象
initContextHolders(request, localeContext, requestAttributes);
try {
// 这里开始调用 DispatcherServlet 中的 doService() 方法
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
// 请求完成之后,会将之前的 context 对象恢复到 ContextHolder 中
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
// 发布事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
这个方法主要的作用就是获取之前设置的 context 对象,然后为当前 request 建立 ContextHolder (ThreadLocal)对象,并在请求完毕之后将其恢复到之前获取的 context 对象(如果为空就直接清除),同时发布处理事件。这里有个疑问,就是既然先获取之前的 context 对象,那么肯定有个入口会设置该值,那么是什么时候设置的呢?而且,请求之后必须要清空 ContextHolder ,那么这里并没有看到相关的清空操作。那么 Spring 是怎么做的呢?大家可以思考一下。
我们继续分析,打开 DispatcherServlet 的 doService() 方法。
doService
doService 是一个覆盖的方法,供父类调用,而该方法只是做一下请求的准备工作,然后将具体请求处理交由 doDispatch 方法。
DispatcherServlet
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// 这里就是对 request 中的属性进行快照备份
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));
}
}
}
// 将相关的解析器、容器保存到 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());
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);
}
RequestPath previousRequestPath = null;
if (this.parseRequestPath) {
previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
ServletRequestPathUtils.parseAndCache(request);
}
try {
// 调用 doDispatcher 处理请求
doDispatch(request, response);
}
finally {
// 同步方法的处理
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// 恢复原始属性快照
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
doDispatch
doDispatch 是处理请求的核心方法
DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 待处理的请求
HttpServletRequest processedRequest = request;
// 请求的完成链,包括拦截器等
HandlerExecutionChain mappedHandler = null;
// 文件上传解析
boolean multipartRequestParsed = false;
// 从当前请求中获取异步处理器(这里获取的其实都是空的处理器,因为程序目前还不知道当前请求是不是异步处理的,后续的文章在进行相关介绍)
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 判断当前请求是否是文件上传请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 获取当前请求的处理器
mappedHandler = getHandler(processedRequest);
// 404
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 获取当前处理的处理器适配器,用于真实的处理请求
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 如果是 Get、Header 方法的话,获取最近修改时间,进行相关的优化操作
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 前置拦截器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 开始处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果是异步处理,直接返回
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 后置拦截器(如果是异步的话,到这之前已经返回了)
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);
}
// 最后的结果、视图、异常的处理,当然拦截器中的 afterCompletion() 方法也会在这一步进行调用(异步处理除外)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// 前边提到一点,如果是异步请求的话,postHandle 以及 afterCompletion 方法是不会生效的,所以这里是针对于异步请求的拦截器处理
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// 如果是文件上传请求,这里会对资源进行处理
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
相关的说明已经详细的在代码中标注了。大体上我们可以总结如下:
- 每个请求都会赋予一个异步管理器。
- 拦截器 preHandle() 方法的调用。
- 真实请求的调用。
- 如果是异步请求,那么拦截器的 postHandle() 和 afterCompletion() 方法是不会生效的,这里需要使用 AsyncHandlerInterceptor 拦截器。
RequestContextFilter
还记得我们之前提到的就是 ContextHolder 是什么时候进行属性配置的吗?其实在请求进来的时候,先经过一轮轮的 Filter 处理,其中之一的 RequestContextFilter 就是用来做这个工作的。我们看一下它的代码:
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
// 进行 ContextHolder 的初始化工作,这里会初始化两个 ContextHolder,一个是 LocaleContextHolder ,一个是 RequestContextHolder。
initContextHolders(request, attributes);
try {
// 请求向下传递
filterChain.doFilter(request, response);
}
finally {
// 请求完成之后,为了避免内存泄漏,这里需要将 ContextHolder 重置。
resetContextHolders();
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
attributes.requestCompleted();
}
}
// 初始化 ContextHolder
private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
}
// 重置 ContextHolder
private void resetContextHolders() {
LocaleContextHolder.resetLocaleContext();
RequestContextHolder.resetRequestAttributes();
}
}
代码很简单,无非就是请求之前初始化 ContextHolder,请求之后清空 ContextHolder。我们在项目中常用的 RequestContextHolder 是怎么来的,到这里我想大家已经豁然开朗了。
尾声
这篇文章我们介绍了请求的处理流程,把 DispatcherServlet 核心方法介绍了一遍。有人可能会提出疑问,请求的调用讲的并不是很详细,@RequestBody 以及 @ResponseBody 的处理都没提到。不要紧,因为接下来的系列文章 【SpringMVC 篇 消息转换器】 会对请求接下来的处理进行详细的介绍。