前文分析了DispatcherServlet的初始化流程之后,现在我们来探究一下DispatcherServlet是如何处理请求的。同样还是那三个Servlet类开始:HttpServletBean、FrameworkServlet和DispatcherServlet。

HttpServletBean

HttpServletBean只参与初始化工作并不参与请求的处理。

FrameworkServlet

Servlet的处理流程我们知道处理请求的入口是service方法。首先从Servlet接口的service方法开始, 然后在Http­Servlet的service方法中根据请求的类型不同将请求路由到了doGet、doHead、doPost等7个方法中,其中doHead、doOptions、doTrace做了默认实现。

FrameworkServlet重写了service、doGet、doPost、doPut、doDelete、doOptions、doTrace方法。在service方法中增加了对PATCH类型的处理,其他类型的请求调用super.service直接交给了父类进行处理; doOptions和doTrace方法可以通过设置 dispatchOptionsRequest 和 dispatchTraceRequest 参数决定是自己处理还是交给父类处理(默认都是交给父类处理, doOptions 会在父类的处理结果中增加 PATCH 类型);doGet、 doPost、 doPut 和 doDelete 都是自己处理。 所有需要自己处理的请求都交给processRequest 方法进行统一处理。

//FrameworkServlet
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
   if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
      processRequest(request, response);
   }
   else {
      super.service(request, response);
   }
}

再来看下每个方法里最主要方法processRequest

//FrameworkServlet
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   long startTime = System.currentTimeMillis();
   Throwable failureCause = null;
    // 获取上一个请求保存的LocaleContext
   LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    //建立新的LocaleContext
   LocaleContext localeContext = buildLocaleContext(request);
    //获取上一个请求保存的RequestAttributes
   RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
   //建立新的RequestAttributes
   ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    //异步请求,可以先跳过,后面文章再说
   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
   asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    //将当前的请求的LocaleContext和RequestAttributes设置到LocaleContextHolder,RequestContextHolder中
   initContextHolders(request, localeContext, requestAttributes);

   try {
       //关键,实际的请求入口
       //模板方法,子类实现
      doService(request, response);
   }
   catch (ServletException | IOException ex) {
      failureCause = ex;
      throw ex;
   }
   catch (Throwable ex) {
      failureCause = ex;
      throw new NestedServletException("Request processing failed", ex);
   }

   finally {
           //恢复
      resetContextHolders(request, previousLocaleContext, previousAttributes);
      if (requestAttributes != null) {
         requestAttributes.requestCompleted();
      }
      logResult(request, response, failureCause, asyncManager);
      //发布ServletRequestHandledEvent消息
      publishRequestHandledEvent(request, response, startTime, failureCause);
   }
}

从上面的流程我们知道这个方法大致在做两件事:

  1. LocaleContextRequestAttributes的设置与恢复
  2. 处理完后发布ServletRequestHandledEvent类型消息

LocaleContext可以用于获取localeRequestAttributes用于管理requestsession属性。如果我们在service层想要获取locale,常见的办法可能是将request传递到service层,但是其实有更简洁的办法就是直接通过LocaleContextHolder#getLocale()来获取。同样RequestContextHolder里面封装了ServletRequestAttributes,我们可以get到requestsessionresponse,因为是对象的引用,所以即使在doService方法里设置了Attribute我们同样也可以拿到属性!

你可能会疑惑为什么后面又执行resetContextHolders,这是因为在servlet外部可能有其他的操作比如Filter,为了不影响那些操作,所以需要恢复。

filter的位置
filter的位置

publishEvents被设置为true的时候,请求结束就会发出这个消息,无论请求是否处理成功都会发布,publishEvents字段可以通过配置改变。我们可以通过监听这个事件来做一些事,比如记录日志等。

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;
      this.webApplicationContext.publishEvent(
            new ServletRequestHandledEvent(this,
                  request.getRequestURI(), request.getRemoteAddr(),
                  request.getMethod(), getServletConfig().getServletName(),
                  WebUtils.getSessionId(request), getUsernameForRequest(request),
                  processingTime, failureCause, response.getStatus()));
   }
}

DispatcherServlet

我们从上面的分析得知这里的入口是doService,但是看代码的处理他其实又交由给doDispatch(request, response);方法处理。进入doDispatch方法前在doService方法前后都做了一些处理:首先判断是不是include请求, 如果是则对request的Attribute 做个快照备份, 等doDispatch 处理完之后(如果不是异步调用且未完成)进行还原, 在做完快照后又对request设置了 一些属性:

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
   logRequest(request);

   //是include请求进行request的attributes快照备份
   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.
   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);
   }

   try {
      doDispatch(request, response);
   }
   finally {
      if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        //还原快照内容
         if (attributesSnapshot != null) {
            restoreAttributesAfterInclude(request, attributesSnapshot);
         }
      }
   }
}

对 request 设置的属性中, 前面 4 个属性 webApplicationContextlocaleResolvertheme­ResolverthemeSource 在之后介绍的 handler 和 view 中需要使用, 到时候再作分析。 后面三个属性都和 flashMap 相关, 主要用于Redirect 转发时参数的传递。

doDispatch方法我们通过剪去细枝末节(比如卸载multipart内容), 保留主要流程:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   
         // Determine handler for the current request.
         mappedHandler = getHandler(processedRequest);
       
         // Determine handler adapter for the current request.
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());


         // Actually invoke the handler.ModelAndView->mv
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());


      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  
}

它们的任务分别是:

  1. 根据request找到Handler;
  2. 根据Handler找到对应的 HandlerAdapter;
  3. 用HandlerAdapter处理Handler;
  4. 调用 processDispatchResult方法处理上面处理之后的结果(包含找到View 并渲染输出给用户),

这里需要解释三个概念: HandlerMapping、 Handler和HandlerAdapter:

  • Handler: 也就是处理器,它直接对应着MVC中的C也就是Controller层, 它的具体表现形式有很多, 可以是类,也可以是方法,如果你能想到别的表现形式也可以使用, 它的类型是Object。 @RequestMapping的所有方法都可以看成一个Handler。只要可以实际处理请求就可以是Handler。
  • HandlerMapping : 是用来查找Handler的,在Spring MVC中会处理很多请求,每个请求都需要一个Handler来处理, 具体接收到一个请求后使用哪个Handler来处理呢?这就是HandlerMapping要做的事情。
  • HandlerAdapter: 从名字上就可以看出它是一个Adapter, 也就是适配器。 因为Spring MVC中的Handler可以是任意的形式,只要能处理请求就OK, 但是 Servlet需要的处理方法的结构却是固定的,都是以 request和response为参数的方法(如doService方法)。 怎么让固定的 Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。

现在我们再来详细看完整的代码

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 {
          //判断是不是Multipart类型的请求,即文件上传
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // 根据request找到handler
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // 根据Handler找到对应的 HandlerAdapter
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // 处理GET HEAD请求的last-modified
         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;
            }
         }
        // 执行Interceptor的prehandle
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // HandlerAdapter使用Handler处理请求
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        // 如果有异步处理,直接返回
         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }
        //当view为空时(比如Hanler是void类型),根据request设置默认view
         applyDefaultViewName(processedRequest, mv);
         //执行Interceptor的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);
      }
      //处理返回结果。包括处理异常,渲染页面,发出完成通知触发interceptor的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()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // 文件处理完成,删除上传请求资源
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}

关于Last-Modified:

当浏览器第一次请求一个资源时,服务端返回状态码200,返回请求的资源的同时HTTP响应头会有一个Last-Modified标记着文件在服务端最后被修改的时间。

浏览器第二次请求上次请求过的资源时,浏览器会在HTTP请求头中添加一个If-Modified-Since的标记,用来询问服务器该时间之后文件是否被修改过

如果服务器端的资源没有变化,则自动返回304状态,使用浏览器缓存,从而保证了浏览器不会重复从服务器端获取资源,也保证了服务器有变化时,客户端能够及时得到最新的资源.

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
      @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
      @Nullable Exception exception) throws Exception {

   boolean errorView = false;
    //请求处理过程出现异常,处理异常
   if (exception != null) {
      if (exception instanceof ModelAndViewDefiningException) {
         logger.debug("ModelAndViewDefiningException encountered", exception);
         mv = ((ModelAndViewDefiningException) exception).getModelAndView();
      }
      else {
         Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
         mv = processHandlerException(request, response, handler, exception);
         errorView = (mv != null);
      }
   }

   // 渲染页面
   if (mv != null && !mv.wasCleared()) {
      render(mv, request, response);
      if (errorView) {
         WebUtils.clearErrorRequestAttributes(request);
      }
   }
   else {
      if (logger.isTraceEnabled()) {
         logger.trace("No view rendering, null ModelAndView returned.");
      }
   }

   if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
      // 启动异步则返回
      return;
   }
    //发出请求处理完成的通知,触发Interceptor的afterCompletion
   if (mappedHandler != null) {
      // Exception (if any) is already handled..
      mappedHandler.triggerAfterCompletion(request, response, null);
   }
}

可以看到processDispatchResult 处理异常的方式 其实就是将相应的错误页面设置到View, 在其中的processHandlerException方法中用到了HandlerExceptionResolver

渲染页面具体在render方法中执行,render中首先对response设置了locale, 过程中使用到了LocaleResolver, 然后判断 View如果是String类型则调用resolveViewName方法使用 ViewResolver得到实际的View, 最后调用Viewrender方法对页面进行具体渲染, 渲染的过程中使用到了ThemeResolver

doDispatcher流程图
doDispatcher流程图

小结

HttpServletBean: 没有参与实际请求的处理。

FrameworkServlet : 将不同类型的请求合并到了processRequest方 法统一处理:processRequest方法中做了三件事:

1.调用了doService模板方法具体处理请求;
2.将当前请求的LocaleContextServletRequestAttributes在处理请求前设置到了LocaleContextHolderRequestContextHolder, 并在请求处理完成后恢复;
3.请求处理完后发布了ServletRequestHandledEvent消息。

DispatcherServlet : 实现了doService方法,给request设置了一些属性并将请求交给doDispatch方法具体处理。DispatcherServlet中的doDispatch方法 完成Spring MVC中请求处理过程的顶层设计,它使用DispatcherServlet中的九大组件完成了具体的请求处理 。

参考

《看透springMvc源代码分析与实践》