本流程分析基于 :
springboot 2.1.1
Tomcat 9.0.13
- 缺省配置
文章目录
应用启动过程中Tomcat
的启动
SpringApplication#run()
SpringApplication#refreshContext(context)
这里
context
是AnnotationConfigServletWebServerApplicationContext
ServletWebServerApplicationContext#refresh()
ServletWebServerApplicationContext#createWebServer()
refresh()
onRefresh()
createWebServer()
ServletWebServerFactory factory = getWebServerFactory();
- 这里会从容器获取
ServletWebServerFactory
类型的bean
,并应用WebServerFactoryCustomizerBeanPostProcessor
。 WebServerFactoryCustomizerBeanPostProcessor
会应用一组WebServerFactoryCustomizer
到ServletWebServerFactory bean
。- 这组
WebServerFactoryCustomizer
是来自容器中该类型的bean
,典型例子 :TomcatWebSocketServletWebServerCustomizer
ServletWebServerFactoryCustomizer
TomcatServletWebServerFactoryCustomizer
TomcatWebServerFactoryCustomizer
HttpEncodingAutoConfiguration$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
,缺省为"",可配置 - 设置
context
docBase
为属性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
的门面Servlet
DispatcherServlet
就是在此过程中被注册到servlet context
中的:
参考SCI
实现类 :DispatcherServletRegistrationBean
名称 :dispatcherServlet
mapping
: ‘/’
实现类org.springframework.web.servlet.DispatcherServlet
实例 : 传入的DispatcherServlet
bean
DispatcherServlet 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
的事件处理队列。
-