Spring5源码14-SpringMVC-DispatcherServlet处理请求核心逻辑

本文详细剖析了SpringMVC的处理流程,从用户请求到DispatcherServlet,再到HandlerMapping、HandlerAdapter、拦截器的调用,以及视图渲染。重点讨论了doDispatch方法中的checkMultipart、getHandler、getHandlerAdapter、Last-Modified缓存处理和拦截器的各个阶段。通过对源码的分析,揭示了SpringMVC如何优雅地处理HTTP请求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. Spring mvc 流程

其时序图:

整体流程图:

SpringMVC 工作流程描述:

  1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet 捕获;
  2. DispatcherServlet 对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以 HandlerExecutionChain 对象的形式返回;
  3. DispatcherServlet 根据获得的 Handler ,选择一个合适的 HandlerAdapter 。(附注:如果成功获得 HandlerAdapter 后,此时将开始执行拦截器的preHandler(…)方法)
  4. 提取 Request 中的模型数据,填充 Handler 入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
    • HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
    • 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
    • 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
    • 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
  5. Handler执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象;
  6. 根据返回的 ModelAndView ,选择一个适合的 ViewResolver (必须是已经注册到Spring容器中的 ViewResolver )返回给 DispatcherServlet ;
  7. ViewResolver 结合 Model 和 View ,来渲染视图
  8. 将渲染结果返回给客户端。

2. HttpServlet & FrameworkServlet

下面我们先看看 DispatcherServlet 的两个父类: HttpServlet 和 FrameworkServlet 。

FrameworkServlet 重写了 HttpServlet 的 service 方法。 我们这里先来看看 FrameworkServlet#service 方法。

@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(request, response); 和 super.service(request, response); 。下面我们一一来看

2.1 HttpServlet#service

我们知道 HttpServlet 类中分别提供了相应的服务方法( doGet、doPost 等),如下图

同时, HttpServlet 会根据请求的不同形式引导到对应导函数中处理,如下 HttpServlet#service(HttpServletRequest, HttpServletResponse) :

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    String method = req.getMethod();
            // 如果是 get 方法
    if (method.equals(METHOD_GET)) {
            //  lastModified  缓存判断
        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 {
            // 如果设置了缓存时间,则判断在ifModifiedSince  之后的时间,是否被修改。
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch (IllegalArgumentException iae) {
                // Invalid date header - proceed as if none was set
                ifModifiedSince = -1;
            }
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // 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);
    }
}
复制代码

而这几个函数最常用的就是 doGet() 和 doPost() 。这两个方法被 FrameworkServlet 重写了。我们来看看在 FrameworkServlet 中的实现。

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {

        processRequest(request, response);
}

@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {

        processRequest(request, response);
}
... 
复制代码

我们可以很清楚的看到,对于大部分的请求,还是依赖于 HttpServlet#service(HttpServletRequest, HttpServletResponse) 来进行一个请求的分发。对于我们常见的 doGet() 和 doPost() 方法都是直接调用 processRequest(request, response); , 而 processRequest 方法 的具体实现在 FrameworkServlet#processRequest 中 。

2.2 FrameworkServlet#processRequest

因此。接下来我们就来看看 org.springframework.web.servlet.FrameworkServlet#processRequest :

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

   // 记录当前时间,用于记录web请求的记录时间
   long startTime = System.currentTimeMillis();
   Throwable failureCause = null;

   // 1234 的目的是为了保证当前线程的 LocaleContext 和 RequestAttributes 在当前请求后还能恢复,所以提取保存
   // 1. 提取当前线程的 LocaleContext  属性
   LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
   // 2. 根据当前的request 创建对应的 LocaleContext ,并绑定到当前线程
   LocaleContext localeContext = buildLocaleContext(request);

   // 3. 提取当前线程的 RequestAttributes 属性
   RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
   // 4. 根据当前的request 创建对应的 RequestAttributes ,并绑定到当前线程
   ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
   asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

   initContextHolders(request, localeContext, requestAttributes);

   try {
      // todo 5. 委托给 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 {
      // 6. 请求结束,恢复线程原状
      resetContextHolders(request, previousLocaleContext, previousAttributes);
      if (requestAttributes != null) {
         requestAttributes.requestCompleted();
      }
      logResult(request, response, failureCause, asyncManager);
      // 发布请求结束的通知事件,无论成功与否
      publishRequestHandledEvent(request, response, startTime, failureCause);
   }
}
复制代码

由于逻辑都被封装到 doService(request, response); 中,所以这里还是比较简单。而doService又被 DispatcherServlet 实现了。因此我们这里来看看 DispatcherServlet#doService 。

  1. DispatcherServlet#doService
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
   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.
   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作用域中 方便获取, 设置一些Spring 上下文
   request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
   request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
   request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
   requ
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值