精尽Spring MVC源码分析 - 一个请求的旅行过程

本文详细解析了Spring MVC处理请求的完整流程,从用户请求到DispatcherServlet,再到FrameworkServlet,接着深入探讨doService、doDispatch等关键方法。通过分析,展示了DispatcherServlet如何协调九大组件工作,以及处理请求的核心逻辑,帮助读者理解Spring MVC的内部机制。
摘要由CSDN通过智能技术生成

我们先来了解一个请求是如何被 Spring MVC 处理的,由于整个流程涉及到的代码非常多,所以本文的重点在于解析整体的流程,主要讲解 DispatcherServlet 这个核心类,弄懂了这个流程后,才能更好的理解具体的源码,回过头再来看则会更加的豁然开朗

整体流程图

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 容器的初始化》文档讲述 FramworkServlet 的 onRefresh 方法时,该方法由 DispatcherServlet 去实现,会初始化九大组件,如何初始化的这里暂时不展开讨论,默认会从 spring-webmvc 下面的 DispatcherServlet.properties 文件中读取组件的实现类,感兴趣可以先阅读一下源码,后续会依次描述

那么接下来就简单介绍一下 DispatcherServlet 和九大组件:

 

Spring MVC 对各个组件的职责划分得比较清晰。DispatcherServlet 负责协调,其他组件则各自做分内之事,互不干扰。经过这样的职责划分,代码会便于维护。同时对于源码阅读者来说,也会很友好。可以降低理解源码的难度,使大家能够快速理清主逻辑。这一点值得我们学习。

ThemeResolver 和 FlashMapManager 组件在该系列文档中不会进行讲解,因为几乎接触不到,感兴趣的可以去 Google 一下,嘻嘻~ 笔者没接触过

FrameworkServlet

虽然在上面的整体流程图中,我们看到请求是直接被 DispatcherServlet 所处理,但是实际上,FrameworkServlet 才是真正的入口,再来回顾一个 DispatcherServlet 的类图,如下:

 

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(HttpServletRequest request, HttpServletResponse response) 方法,处理请求。因为 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> 记录异常,用于保存处理请求过程中发送的异常
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倾听铃的声

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值