本流程分析基于 :
springboot 2.1.1Tomcat 9.0.13- 缺省配置
文章目录
应用启动过程中Tomcat的启动
SpringApplication#run()
SpringApplication#refreshContext(context)这里
context是AnnotationConfigServletWebServerApplicationContextServletWebServerApplicationContext#refresh()
ServletWebServerApplicationContext#createWebServer()
refresh()onRefresh()createWebServer()ServletWebServerFactory factory = getWebServerFactory();- 这里会从容器获取
ServletWebServerFactory类型的bean,并应用WebServerFactoryCustomizerBeanPostProcessor。 WebServerFactoryCustomizerBeanPostProcessor会应用一组WebServerFactoryCustomizer到ServletWebServerFactory bean。- 这组
WebServerFactoryCustomizer是来自容器中该类型的bean,典型例子 :TomcatWebSocketServletWebServerCustomizerServletWebServerFactoryCustomizerTomcatServletWebServerFactoryCustomizerTomcatWebServerFactoryCustomizerHttpEncodingAutoConfiguration$LocaleCharsetMappingsCustomizer
- 该应用过程中使用的信息就包含来自配置文件的
ServerProperties信息。
- 这里会从容器获取
this.webServer = factory.getWebServer(getSelfInitializer());这里
getSelfInitializer()返回一个lambda表达式,对应函数式接口ServletContextInitializer
这里getSelfInitializer()对应的逻辑是方法selfInitialize(ServletContext servletContext)- 方法
selfInitialize()会随后在tomcat StandardContext启动中执行SCI onStartup时被调用 - 方法
selfInitialize()执行时会获取Spring容器中所有实现了SCI接口的bean并调用它们的onStartup()方法 - 这些
SCI bean主要是往ServletContext中注册Servlet,Filter,DelegatingFilterProxy Filter,EventListener等
- 方法
- 将真正的
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 -
关联
connector到tomcat 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;- 新建的
StandardEngine实例缺省使用localhost:8080; - 新建的
StandardEngine实例会设置成上面新建service的关联container;
这里
getHost()也会触发tomcat内部建立一个StandardHost实例,名称为localhost;- 新建的
StandardHost实例会设置成上面新建engine的孩子container;
- 新建的
-
定制
tomcat engine, 主要是为其增加Valve : this.engineValves(缺省没有) -
为
tomcat service添加额外的connector:this.additionalTomcatConnectors(缺省没有) -
prepareContext:准备tomcat host的StandardContext, 这里使用的是Springboot的实现TomcatEmbeddedContext- 创建
TomcatEmbeddedContext实例, 这个context对应一个web应用,也是整个Spring Boot Servlet应用中唯一的一个web应用 - 设置
context名称为属性contextPath,缺省为 “”,可配置 - 设置
context显示名称为属性displayName,缺省为 “application”,可配置 - 设置
context路径为属性contextPath,缺省为"",可配置 - 设置
contextdocBase为属性documentRoot(缺省为null) ,如果documentRoot为null则使用一个新创建的临时目录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有以下三个来源:#prepareContext方法的参数ServletContextInitializer[] initializers,这是从Spring IoC容器中找到的bean#mergeInitializers方法内部定义的两个ServletContextInitializer
一个是lambda表达式构造的匿名ServletContextInitializer,用于设置initParameters到ServletContext,一个是SessionConfiguringInitializer(TomcatServletWebServerFactory的嵌套内部类)。- 当前
TomcatServletWebServerFactory实例的属性initializers,缺省无
- 将上面
context设置为host的child, (也是唯一的一个child) configureContext(Context context,ServletContextInitializer[] initializers)会基于参数
initializers创建一个TomcatStarter对象(实现了接口ServletContainerInitializer),并将其跟context关联
会向context配置一个ErrorPage处理,对应url为/error
配置context的mime 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被执行,从而完成Spring向servlet context注册Servlet/Filter的
任务。Spring Web MVC的门面ServletDispatcherServlet就是在此过程中被注册到servlet context中的:
参考SCI实现类 :DispatcherServletRegistrationBean
名称 :dispatcherServlet
mapping: ‘/’
实现类org.springframework.web.servlet.DispatcherServlet
实例 : 传入的DispatcherServletbeanDispatcherServlet bean是通过DispatcherServletAutoConfiguration根据Web MVC配置属性自动构建的
该注册同时设置了文件上传有关参数:MultipartConfigElement注意 : 从这里可以看出
dispatcherServlet跟default Servlet的mapping是一样的,没错。
但是因为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(), 这里selectorPool是NioSelectorPool
-
-
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处理流程的入口:- 任何请求数据的分析尚未开始,即将开始;
- 从
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 2.1.1中Tomcat 9.0.13的启动流程,涵盖从SpringApplication#run()到TomcatWebServer#start()的全过程,包括WebServerFactoryCustomizer的应用、Connector的配置、Context的准备与启动,以及线程池、事件监听器和Servlet的注册。
384





