Springboot Web应用Tomcat启动流程概述

本文解析了SpringBoot 2.1.1中Tomcat 9.0.13的启动流程,涵盖从SpringApplication#run()到TomcatWebServer#start()的全过程,包括WebServerFactoryCustomizer的应用、Connector的配置、Context的准备与启动,以及线程池、事件监听器和Servlet的注册。

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

本流程分析基于 :

  • springboot 2.1.1
  • Tomcat 9.0.13
  • 缺省配置

应用启动过程中Tomcat的启动

SpringApplication#run()

  • SpringApplication#refreshContext(context)

    这里 contextAnnotationConfigServletWebServerApplicationContext

  • ServletWebServerApplicationContext#refresh()

ServletWebServerApplicationContext#createWebServer()

  • refresh()
    • onRefresh()
      • createWebServer()
        1. ServletWebServerFactory factory = getWebServerFactory();
          1. 这里会从容器获取ServletWebServerFactory 类型的bean,并应用WebServerFactoryCustomizerBeanPostProcessor
          2. WebServerFactoryCustomizerBeanPostProcessor会应用一组WebServerFactoryCustomizerServletWebServerFactory bean
          3. 这组WebServerFactoryCustomizer是来自容器中该类型的bean,典型例子 :
            • TomcatWebSocketServletWebServerCustomizer
            • ServletWebServerFactoryCustomizer
            • TomcatServletWebServerFactoryCustomizer
            • TomcatWebServerFactoryCustomizer
            • HttpEncodingAutoConfiguration$LocaleCharsetMappingsCustomizer
          4. 该应用过程中使用的信息就包含来自配置文件的ServerProperties信息。
        2. this.webServer = factory.getWebServer(getSelfInitializer());

          这里getSelfInitializer()返回一个lambda表达式,对应函数式接口ServletContextInitializer
          这里getSelfInitializer() 对应的逻辑是方法 selfInitialize(ServletContext servletContext)

          1. 方法selfInitialize()会随后在tomcat StandardContext启动中执行SCI onStartup时被调用
          2. 方法selfInitialize()执行时会获取Spring容器中所有实现了SCI接口的bean并调用它们的onStartup()方法
          3. 这些SCI bean主要是往ServletContext中注册Servlet,Filter,DelegatingFilterProxy Filter,EventListener
        3. 将真正的servletContext/servletConfig属性源设置到应用上下文的Environment中去

          环境对象中相应属性的名字为 servletContextInitParams,servletConfigInitParams

TomcatServletWebServerFactory#getWebServer(initializers)

  • 这里参数 initializers 包含一组外部配置的 SCI, 用于初始化阶段注册 Servlet,Filter

  • Tomcat tomcat = new Tomcat();

  • 设置 tomcat baseDir, 使用指定位置 this.baseDirectory, 或者临时目录 tomcat

  • 创建 Connector connector=new Connector(this.protocol)

  • this.protocol 缺省是 org.apache.coyote.http11.Http11NioProtocol

  • 关联 connectortomcat service : tomcat.getService().addConnector(connector)

    这里 getService() 其实是return getServer().findServices()[0],触发了getServer(),
    这里 getServer() 会触发 tomcat 内部建立一个 StandardServer 实例,停止端口使用8005,
    新建的 StandardServer 实例内含一个新建的 StandardService 实例,名称为 Tomcat;

  • 定制 connector (主要参照配置ServerProperties);

    • 根据配置设置监听端口 (对应配置项:server.port,缺省值8080)
    • 根据配置设置URL encoding
    • bindOnInit:false
    • 根据配置设置SSL (对应配置项:server.ssl.*)
    • 根据配置进行压缩设置 (对应配置项:server.compression.enabled,缺省值false)
      • 根据配置使用一个CompressionConnectorCustomizer进行定制
    • 应用各个TomcatConnectorCustomizer
  • tomcat上设置host自动部署为 false :tomcat.getHost().setAutoDeploy(false)

    这里 getHost() 会触发 tomcat 内部建立一个 StandardEngine 实例,名称为 Tomcat;

    1. 新建的 StandardEngine 实例缺省使用 localhost:8080;
    2. 新建的 StandardEngine 实例会设置成上面新建 service 的关联 container;

    这里 getHost() 也会触发 tomcat 内部建立一个 StandardHost 实例,名称为 localhost;

    1. 新建的 StandardHost 实例会设置成上面新建 engine 的孩子 container ;
  • 定制 tomcat engine, 主要是为其增加 Valve : this.engineValves (缺省没有)

  • tomcat service 添加额外的 connector : this.additionalTomcatConnectors (缺省没有)

  • prepareContext:准备 tomcat hostStandardContext, 这里使用的是 Springboot 的实现 TomcatEmbeddedContext

    • 创建 TomcatEmbeddedContext 实例, 这个 context 对应一个 web 应用,也是整个Spring Boot Servlet应用中唯一的一个 web 应用
    • 设置 context 名称为属性 contextPath,缺省为 “”,可配置
    • 设置 context 显示名称为属性displayName,缺省为 “application”,可配置
    • 设置 context 路径为属性contextPath,缺省为"",可配置
    • 设置 context docBase 为属性documentRoot (缺省为null) ,如果documentRootnull则使用一个新创建的临时目录 tomcat-base
    • 增加缺省的 locale encoding mapping 参数 : 英语/UTF-8, 法语/UTF-8;
    • 设置 context loader 为一个新建的 WebappLoader
    • 添加缺省 Servlet : org.apache.catalina.servlets.DefaultServlet

      名称 : default
      mapping :/
      initParameter : debug = 0
      initParameter : listings = false
      loadOnStartup : 1
      overridable : true (注意:这里标记为可以被覆盖)

    • 检测是否需要添加Jsp有关 Servlet : 缺省没有引用Jsp有关包,因此不使用Jsp也不添加该Servlet
    • mergeInitializers(initializers):合并所有要使用的ServletContextInitializer交给下面的configureContext使用

      这里需要合并的ServletContextInitializer 有以下三个来源:

      1. #prepareContext方法的参数ServletContextInitializer[] initializers,这是从Spring IoC容器中找到的bean
      2. #mergeInitializers方法内部定义的两个ServletContextInitializer
        一个是lambda表达式构造的匿名ServletContextInitializer,用于设置initParametersServletContext,一个是SessionConfiguringInitializer(TomcatServletWebServerFactory的嵌套内部类)。
      3. 当前TomcatServletWebServerFactory实例的属性initializers,缺省无
    • 将上面context设置为 hostchild , (也是唯一的一个 child)
    • configureContext(Context context,ServletContextInitializer[] initializers)

      会基于参数initializers创建一个TomcatStarter对象(实现了接口ServletContainerInitializer),并将其跟context关联
      会向context配置一个ErrorPage处理,对应url为/error
      配置contextmime mapping为属性mimeMappings,可配置,缺省为MimeMappings.DEFAULT
      customizeErrorReportValve() : 为 context.parent, 也就是 host 定制添加一个 ErrorReportValve

    • 配置session

      超时时间 : 缺省为 30 分钟
      HttpOnly : 如果被配置属性设置使用配置属性值,否则缺省为 true
      持久化管理器 : 如果配置属性指定了session持久化(缺省不指定,所以缺省不使用这里的session持久化机制),则启用并配置session管理器Manager,缺省实现类为StandardManager
      增加生命周期事件监听器:DisablePersistSessionListener

    • 基于上面建立的 tomcat 对象继续创建和初始化 WebServer : getTomcatWebServert(tomcat)

TomcatServletWebServerFactory#getTomcatWebServer(tomcat)

  • return new TomcatWebServer(tomcat, getPort() >= 0)
  • TomcatWebServer 构造函数中会调用自己的初始化方法 initialize()
    • Service上绑定的Connector(可能有多个)删除并保存下来(保存到属性Map<Service, Connector[]> serviceConnectors)

      这里删除Connector的目的是在下面tomcat.start()启动过程中避免启动相应的Connector,而Connector通常
      启动意味着可以接收请求了,而此时Service尚未启动结束,所以要避免在Service启动过程中启动Connector
      而是将其延后启动。

      这里的的"删除"实际上是提供了一个事件响应函数,该事件响应函数会在context的生命周期事件START_EVENT发生时真正删除Service上的Connector

    • 此方法会调用 tomcat.start() 从而启动 Server/Service/Engine/Host/Context/Wrapper各级容器

    • 注意这里 Engine/Host/Context 之间的启动调用采用异步方式,其他采用同步方式

    • 启动过程中Context启动时,上面提到的方法ServletWebServerApplicationContext#selfInitialize()会被调用

      selfInitialize()执行时,bean形式存在的各个SCI被执行,从而完成Springservlet context注册Servlet/Filter
      任务。

      Spring Web MVC 的门面Servlet DispatcherServlet就是在此过程中被注册到servlet context中的:
      参考SCI实现类 : DispatcherServletRegistrationBean
      名称 : dispatcherServlet
      mapping : ‘/’
      实现类 org.springframework.web.servlet.DispatcherServlet
      实例 : 传入的 DispatcherServlet bean

      DispatcherServlet bean是通过DispatcherServletAutoConfiguration根据Web MVC配置属性自动构建的
      该注册同时设置了文件上传有关参数:MultipartConfigElement

      注意 : 从这里可以看出 dispatcherServletdefault Servletmapping 是一样的,没错。
      但是因为dispatcherServlet注册在后,因此tomcat会使用它覆盖掉default Servlet

    • startDaemonAwaitThread() 启动一个非daemon线程阻止程序马上结束

      该线程每10秒检查一次tomcat是否接收到结束信号,如果接收到,则结束执行。
      设置该线程的原因 : 所有的tomcat线程都是daemon线程,如果没有这样一个非daemon线程,
      整个程序会马上退出。

ServletWebServerApplicationContext#startWebServer()

  • refresh()
    • finishRefresh()
      • startWebServer()
        • this.webServer.start() 这里的webServer就是上面创建和初始化的TomcatWebServer实例
      • 发布事件ServletWebServerInitializedEvent

TomcatWebServer#start()

  • 将上面从Service 上删除的Connector添加回来

    • 因为之前已经启动了Service,所以这里添加过程中会直接启动所添加的Connector:connector.start()
  • Connector#start()

    • protocolHandler.start() 这里protocolHandler是一个Http11NioProtocol
  • Http11NioProtocol#start()

    • endpoint.start() 这里 endpoint 是一个 NioEndpoint
  • NioEndpoint#start()

    • bind() 绑定服务端口

      • initServerSocket() : 创建服务器套接字,绑定到所配置的端口,比如 : 8080

        注意这里设置服务套接字设置为阻塞模式
        serverSock.configureBlocking(true) //mimic APR behavior

      • 初始化需要创建的acceptor/poller线程的个数

        缺省 acceptor/poller 分别对应 : acceptorThreadCount:1,pollerThreadCount:2
        pollerThreadCount最少是1,如果CPU有多核供当前JVM使用,则使用2

      • 如果启用了SSL,进行相关SSL初始化

      • selectorPool.open() , 这里 selectorPoolNioSelectorPool

    • createExecutor()创建工作线程池 executor

      所创建的工作线程池executor是一个ThreadPoolExecutor,绑定一个新建任务队列TaskQueue
      这个任务队列类是org.apache.tomcat.util.threads.TaskQueue
      每个工作线程的名称使用前缀:http-nio-8080-exec-

    • initializeConnectionLatch() 初始化最大连接数控制器

      缺省最大连接数量为10000

    • 创建poller线程并启动

      poller 线程名称使用前缀 :http-nio-8080-ClientPoller-
      poller线程使用的Runnable实现类是NioEndpoint嵌套类Poller
      poller线程都是daemon线程。
      poller线程负责从自己的事件队列中取出一个个分配给自己需要处理的请求,当上面有NIO事件发生时,
      将相应的请求封装成一个SocketProcessor 交给 executor 线程池中的某个工作线程来处理。
      这里的SocketProcessor``实现了Runnable接口,是NioEndpoint的嵌套类。
      SocketProcessor会使用一个NioEndpoint.ConnectionHandler处理该套接字请求上的事件。
      ConnectionHandler会新建一个Http11Processor并委托它处理指定任务。

      严格来讲,Http11Processor才是一个请求进入真正意义上的Servlet处理流程的入口:

      1. 任何请求数据的分析尚未开始,即将开始;
      2. Servlet处理流程中可见的Request/Response对象也是此时出现的;
    • 创建acceptor线程并启动

      acceptor线程名称使用 : http-nio-8080-Acceptor-0
      acceptor线程使用的Runnable实现类是org.apache.tomcat.util.net.Acceptor
      acceptor线程都是daemon线程
      acceptor线程只负责接收请求的有关逻辑,请求到达时,经过相应的NIO操作有关封装,
      被进一步包装成一个PollerEvent事件然后添加到某个poller的事件处理队列。

相关文章

缺省配置Springboot Web应用中tomcat的启动过程

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值