写点什么

Spring Boot「20」从 DispatchServlet 开始一个请求的处理过程

作者:Samson
  • 2022-10-31
    上海
  • 本文字数:4841 字

    阅读完需:约 16 分钟

01-DispatchServlet

DispatchServlet 是 Spring MVC 的核心,其遵循前端控制器设计模式。它是 Spring 实现的一个 servlet,在 servlet 容器中,负责将请求路由到其他的组件当中。


Java ™ Servlet Specification Version 3.0 中对 servlet 和 servlet 容器的定义如下:

A servlet is a Java™ technology-based Web component, managed by a container, that generates dynamic content.

The servlet container is a part of a Web server or application server that provides the network services over which requests and responses are sent, decodes MIME-based requests, and formats MIME-based responses. A servlet container also contains and manages servlets through their lifecycle.

01.1-DispatcherServlet 处理请求的基本流程

[1] 描述了一个请求到达 DispatchServlet 后的基本流程:


  1. 请求离开浏览器到达 DispatchServlet

  2. DispatchServlet 负责根据请求携带的 URL 信息,确定请求被路由到哪里

  3. DispatchServlet 将请求转交给 2.中选中的控制器

  4. 控制器返回模型和视图名给 DispatchServlet

  5. DispatchServlet 根据视图名确定具体的视图实现

  6. 具体的实现将 4.中的逻辑视图渲染成真正的视图

  7. 视图被返回给浏览器


了解 DispatchServlet 处理请求的基本流程后,不纠结具体的实现细节,让我们先来看一下在 Spring MVC 中如何配置 DispatchServlet?

01.2-Spring MVC 中配置 DispatcherServlet

  1. 通过实现WebApplicationInitializer接口的方式


    public class MyWebApplicationInitializer implements WebApplicationInitializer {            @Override        public void onStartup(javax.servlet.ServletContext servletContext) throws ServletException {            // Load Spring web application configuration            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();            context.register(AppConfig.class);                // Create and register the DispatcherServlet            DispatcherServlet servlet = new DispatcherServlet(context);            ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);            registration.setLoadOnStartup(1);            registration.addMapping("/app/*");        }        }
复制代码


Servlet容器在启动时,会通过SPI机制,回调到`onStartup`方法,具体参考 [Spring MVC 「2」WebApplicationInitializer的工作原理](https://juejin.cn/post/7056695212399132703)。
回到上面的代码,在`onStartup`方法中,我们创建了一个DispatcherServlet,并将其注册在`servletContext`中,并将`/app/*`与其绑定。
复制代码


  1. 通过继承AbstractAnnotationConfigDispatcherServletInitializer类的方式


    public class MyDispatchServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {            @Override        protected Class<?>[] getRootConfigClasses() {            return new Class[0];        }            /**         * 仅在需要应用程序上下文需要层次结构时需要         * 如果应用仅需要一个上下文,则可以返回 null,         * 而由getRootConfigClasses返回应用上下文         */        @Override        protected Class<?>[] getServletConfigClasses() {            return new Class[0];        }            @Override        protected String[] getServletMappings() {            return new String[0];        }    }
复制代码


通过对`AbstractAnnotationConfigDispatcherServletInitializer`继承层次分析发现,其实现了`WebApplicationInitializer`接口,最终也归结为方式的另类形式,只不过省去了手动实例化DispatcherServlet的麻烦。
![Untitled](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6cbb089c7a6143c58ca7c38e91025419~tplv-k3u1fbpfcp-zoom-1.image)
与方式1相比,更推荐方式2,因为使用起来更方便。
复制代码


  1. 通过实现WebMvcConfigurer接口的方式

  2. 方式 3 与前面两种方式不太一样,具体不同之处逐个进行分析。

  3. 首先,实现WebMvcConfigurer接口并不能实例化 DispatcherServlet 对象。

  4. 其次,WebMvcConfigurer仅仅是为了让用户可以修改 DispatcherServlet 在处理请求时使用的各种对象,例如拦截器(addInterceptors())、视图解析器(configureViewResolvers())等等

  5. 最后,使用 Spring MVC 时,通常会在配置文件上添加@EnableWebMvc注解。该注解会通过@Import引入另一个配置文件org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfigurationDelegatingWebMvcConfiguration中定义了setConfigurers方法:


    /**    * 自动从容器中请求所有实现了 WebMvcConfigurer 接口的Bean    * 所以,如果我们自己实现了 WebMvcConfigurer 接口,    * 实现类会在最终添加到 configurers 中    */    @Autowired(required = false)    public void setConfigurers(List<WebMvcConfigurer> configurers) {      if (!CollectionUtils.isEmpty(configurers)) {        this.configurers.addWebMvcConfigurers(configurers);      }    }
复制代码


[1] Spring in Action[2] Specifications

02-结合源码分析 DispatcherServlet 处理请求的具体流程

02.1-DispatcherServlet 初始化过程

当 Servlet 容器启动时,会通过 SPI 机制回调到org.springframework.web.SpringServletContainerInitializer#onStartup方法。在该方法中会遍历调用类路径下所有实现了WebApplicationInitializer接口的类的onStartup方法,最终程序流会进入我们自己的初始化类中。


以 01.2 节中介绍的第 2 中方式为例(即通过继承AbstractAnnotationConfigDispatcherServletInitializer来初始化 DispatcherServlet) ,程序流会进入到我们的实现类中MyDispatchServletInitializer


// 该方法继承自抽象类 AbstractDispatcherServletInitializer public void onStartup(ServletContext servletContext) throws ServletException {  /**  * 实际上会调用继承自父类 AbstractContextLoaderInitializer 的   * registerContextLoaderListener方法,  * 主要用来创建 rootApplicationContext 和创建监听器  */  super.onStartup(servletContext);   /**  * 主要用来创建 servletContext 及 DispatcherServlet对象  * 将 DispatcherServlet 对象绑定到某个路径上(由getServletMappings()指定)  */  this.registerDispatcherServlet(servletContext);}
复制代码

02.2-一个请求的处理流程

DispatcherServlet 是 Servlet 接口的一个实现,其接口及类实现关系如下图所示:



当请求离开浏览器进入到 tomcat 以后,最终会被 Servlet 容器传递给 DispatcherServlet 继承来的方法HttpServlet#service中。在service方法中,根据请求中的 HTTP 方法,会交由特定的方法去处理:


String method = req.getMethod();long lastModified;if (method.equals("GET")) {  lastModified = this.getLastModified(req);  if (lastModified == -1L) {    this.doGet(req, resp);  } else {    long ifModifiedSince = req.getDateHeader("If-Modified-Since");    if (ifModifiedSince < lastModified) {      this.maybeSetLastModified(resp, lastModified);      this.doGet(req, resp);    } else {      resp.setStatus(304);    }  }} else if (method.equals("HEAD")) {  lastModified = this.getLastModified(req);  this.maybeSetLastModified(resp, lastModified);  this.doHead(req, resp);} else if (method.equals("POST")) {  this.doPost(req, resp);} else if (method.equals("PUT")) {  this.doPut(req, resp);} else if (method.equals("DELETE")) {  this.doDelete(req, resp);} else if (method.equals("OPTIONS")) {  this.doOptions(req, resp);} else if (method.equals("TRACE")) {  this.doTrace(req, resp);} else {  String errMsg = lStrings.getString("http.method_not_implemented");  Object[] errArgs = new Object[]{method};  errMsg = MessageFormat.format(errMsg, errArgs);  resp.sendError(501, errMsg);}
复制代码


后续代码以doGet为例,请求被路由到继承来的FrameworkServlet#processRequest方法中,然后到达DispatcherServlet#doService方法中,在设置过一系列属性后(例如),请求最终到达DispatcherServlet#doDispatch方法中。


protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {  HttpServletRequest processedRequest = request;  HandlerExecutionChain mappedHandler = null;  boolean multipartRequestParsed = false;  WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try { try { ModelAndView mv = null; Object dispatchException = null;
try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; // 1. 从handlerMapping中查找处理器(遇到的第一个合适的) mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } // 2. 从适配器中找到第一个匹配的适配器 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); 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; } // 3. 处理请求 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; }
this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); }
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); }
} finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); }
}}
复制代码


至此,请求的处理过程基本完毕。当然,我们依然忽略了许多的处理细节,这些将在后面的文章中详细分析。

发布于: 刚刚阅读数: 4
用户头像

Samson

关注

还未添加个人签名 2019-07-22 加入

InfoQ签约作者 | 阿里云社区签约作者

评论

发布
暂无评论
Spring Boot「20」从 DispatchServlet 开始一个请求的处理过程_Java_Samson_InfoQ写作社区