• SpringBoot源码学习4——SpringBoot内嵌Tomcat启动流程源码分析


    系列文章目录和关于我

    零丶引入#

    我在初学spring的时候,很懵逼,因为整个项目中不存在main方法,让我有点摸不着头脑。那时候我知道有个东西叫tomcat是它监听了端口,解析了协议调到了我的servlet。

    在我初学SpringBoot的时候,很懵逼,有main方法了,但是tomcat在哪里呢,又是如何启动起来的?。

    一丶原生tomcat启动流程#

    image-20230507213047804

    Tomcat总体架构,启动流程与处理请求流程中学习了tomcat总体架构和启动流程。

    在springboot内嵌tomcat中则不再使用BootStrap->Catalina这种方式进行启动,而是跨过这一层直接启动了Server。

    二丶SpringBoot根据上下文推断ApplicationContext类型#

    下图是SpringBoot的启动流程,其中红色框框是SpringBoot推断ApplicationContext的步骤

    image-20230514175953760

    这是我们学习springboot内嵌tomcat启动的切入点。

    1.推断当前webApplication类型#

    image-20230514180313080

    在springboot启动的时候会根据classpath推断当前web应用类型。如果在classpath中存在org.springframework.web.reactive.DispatcherHandler那么会视为REACTIVE类型,如果存在javax.servlet.Servlet,org.springframework.web.context.ConfigurableWebApplicationContext那么会视为SERVLET类型

    2.获取webApplication类型对应ApplicationContext#

    image-20230514180717619

    SpringBoot会使用ApplicationContextFactory去构建出一个ApplicationContext,一般默认使用DefaultApplicationContextFactory,DefaultApplicationContextFactory会从spring.factories中找出所有的ApplicationContextFactory实现类根据webApplication类型去创建

    image-20230514181524480

    其中有SERVLET类型对应的AnnotationConfigServletWebServerApplicationContext内部类Factory和REACTIVE类型对应的AnnotationConfigReactiveWebServerApplicationContext内部类Factory。

    在不使用spring 响应式编程的情况下这里都会使用AnnotationConfigServletWebServerApplicationContext内部类Factory去构建

    image-20230514182028855

    也就是说SpringBoot的启动会使用SpringApplication构建出AnnotationConfigServletWebServerApplicationContext最为Spring上下文。

    三丶AnnotationConfigServletWebServerApplicationContext刷新触发tomcat启动#

    image-20230514182154614

    在SpringBoot启动的过程中,SpringApplication会触发Spring上下文(ApplicationContext,也就是AnnotationConfigServletWebServerApplicationContext)的刷新

    image-20230514182543567

    AnnotationConfigServletWebServerApplicationContext是AbstractApplicationContext的子类,其refresh刷新方法由AbstractApplicationContext进行了实现。大致流程如下image-20230514182722665

    其中onRefresh钩子方法便会触发Tomcat的启动,onRefrsh由 AnnotationConfigServletWebServerApplicationContext父类ServletWebServerApplicationContext进行了实现

    image-20230514183330568

    四丶createWebServer创建web服务器#

    下图是SpringBoot启动web服务器的全流程

    image-20230514184725968

    1.ServletWebServerFactory#

    image-20230514185233968

    WebServerFactory是一个标记接口,ServletWebServerFactory中定义了方法getWebServer(ServletContextInitializer... initializers)来创建WebServer,如参ServletContextInitializer是函数式接口,具备方法onStartup来进行回调。

    默认配置下SpringBoot将使用TomcatServletWebServerFactory来创建WebServer。

    2.WebServer#

    WebServer是springboot对服务器的抽象,具备start,stop,getPort,shutDownGracefully(优雅停)方法。

    image-20230514185904908

    3.创建TomcatWebServer流程#

    image-20230514225504751

    组装生成TomcatServer主要依赖于Tomcat这个类,Tomcat是嵌入式tomcat启动器,提供众多api来组装tomcat服务器。这一步其实就是在组装tomcat容器模型。

    image-20230515003051333

    组装的过程依赖于Tomcat这个类提供的api

    image-20230514230446230

    可以看到Tomcat中具备Server属性,这代表了web应用服务器

    在Server中具备Service数组属性表示web应用服务器中众多的服务

    这里只有一个服务——StandardService,其中包含Connector数组和Engine数组

    • Connector

      Connector负责监听客户端请求,返回响应数据。

      image-20230507193102849

      Connector内部由ProtocalHandler属性,其使用AbstractEndpoint监听端口,并将请求交给Processor处理

    • Engine

      Engine负责处理具体的请求。

      image-20230514231410119

      Engine是一个Container内部使用HashMap维护Host(key是hostName,value是Host对象)

      image-20230514231542501

      Host也是一个Container,内部同一使用HashMap维护Context(key是上下文名称,value是Context对象)

      image-20230514231750907

      最终将设置Tomcat对象到TomcatWebServer属性上,并调用Tomcat#start启动Tomcat服务器

    五丶TomcatWebServer启动Tomcat#

    image-20230514233046717

    TomcatWebServer会调用Tomcat.start方法来启动Tomcat,整个启动流程和原生Tomcat一致

    六丶DispatcherServlet是怎么被加到tomcat中的#

    SpringBoot中如果使用web-stater,那么会引入DispatcherServlet的自动装配,这也就是为什么Tomcat接收到的请求会来到DispatcherServlet,然后由DispatcherServlet反射调用到Controller的方法。

    那么DispatcherServlet是什么时候被加入到Tomcat中的呢?

    TomcatReactiveWebServerFactory#configureContext方法中会注册TomcatStarterTomcatEmbeddedContext

    image-20230514235257190

    TomcatEmbeddedContextStandardContext的子类,在TomcatEmbeddedContext被调用start的时候,会拿出所有的ServletContainerInitializer调用其onStartup

    这里便会拿到TomcatStarterTomcatStarter使用ServletContextInitializer数组记录了ServletWebServerApplicationContext#selfInitialize方法,从而实现ServletWebServerApplicationContext#selfInitialize的回调

    SpringBoot之所以这么做是因为ServletContainerInitializer是Servlet规范接口,而ServletContextInitializerSpringBoot定义的接口,利用TomcatStarter将SpringBoot定义的接口嫁接到Servlet定义的规范中从而保证当用户将SpringBoot打包成war包也能触发ServletWebServerApplicationContext#selfInitialize

    那么ServletWebServerApplicationContext#selfInitialize做了什么

    private void selfInitialize(ServletContext servletContext) throws ServletException {
        // <1> 将当前 Spring 应用上下文设置到 ServletContext 上下文的属性中
        // 同时将 ServletContext 上下文设置到 Spring 应用上下文中
        prepareWebApplicationContext(servletContext);
        // <2> 向 Spring 应用上下文注册一个 ServletContextScope 对象(ServletContext 的封装)(这就是application这种bean作用域生效的本原因)
        registerApplicationScope(servletContext);
        // <3> 向 Spring 应用上下文注册 `contextParameters` 和 `contextAttributes` 属性
        WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
        /**
         * <4> 【重点】先从 Spring 应用上下文找到所有的 ServletContextInitializer
         * 也就会找到各种  RegistrationBean,然后依次调用他们的 `onStartup` 方法,向 ServletContext 上下文注册 Servlet、Filter 和 EventListener
         * 例如 DispatcherServletAutoConfiguration DispatcherServletRegistrationBean 就会注册 @link DispatcherServlet 对象
          * 所以这里执行完了,也就启动了 Tomcat,同时注册了所有的 Servlet,那么 Web 应用准备就绪了
         */
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
    

    Spring会从BeanFactory拿到所有ServletContextInitializer的实现

    image-20230515000818926

    image-20230515001104249

    注册其实都是回调这些ServletContextInitializer的onStartup方法,DispatcherServletRegistrationBean则会将DispatcherServlet注册到ServletContext中。

    image-20230515002829734

    TomcatEmbeddedContext会将DispatcherServlet保证成Wrapper加入到TomcatEmbeddedContext中去。

    image-20230515003153142

    七丶总结#

    这一波学习,让我深刻的理解了Tomcat容器模型,也了解到SpringBoot中使用Filter或者Servlet的时候,为什么要向Spring注入对应的RegsitrationBean,因为只有这样ServletWebServerApplicationContext才能从容器中获取到RegsitrationBean并注册到Tomcat中。

  • 相关阅读:
    动手学深度学习—网络中的网络NiN(代码详解)
    LeetCode 1194.锦标赛优胜者
    Selenium基础 — 浏览器弹窗操作
    linux驱动开发day6--(epoll实现IO多路复用、信号驱动IO、设备树以及节点和属性解析相关API使用)
    云原生周刊:Kubernetes v1.29 正式发布 | 2023.12.18
    前端使用 Konva 实现可视化设计器(8)- 预览框
    linux内核的块设备驱动框架详解
    值类型与引用类型的区别,以及string类型的解释
    Java并发编程-前期准备知识-上
    警惕,11月这6本期刊已被剔除SCI/SSCI
  • 原文地址:https://www.cnblogs.com/cuzzz/p/17400667.html