我们都知道,默认情况下 SpringBoot 应用可以打jar包运行,在启动IOC容器时引导启动嵌入式Web容器(Tomcat),而且根据之前的IOC原理,我们知道在 ServletWebServerApplicationContext
的 onRefresh
方法中创建了嵌入式 Tomcat,这一篇咱来展开研究嵌入式 Tomcat 创建的过程。
0. 前置知识
Tomcat 的内部核心结构包含如下组件:
Service:一个 Tomcat-Server 可以有多个 Service , Service 中包含下面的所有组件。
Connector:用于与客户端交互,接收客户端的请求,并将结果响应给客户端。
Engine:负责处理来自 Service 中的 Connector 的所有请求。
Context:可理解为应用,一个主机下有多个应用,一个应用中有多个 Servlet (可以简单理解为 webapps 中一个文件夹代表一个 Context )。
1. ServletWebServerApplicationContext#onRefresh
复制 protected void onRefresh() {
super . onRefresh ();
try {
createWebServer() ;
}
catch ( Throwable ex) {
throw new ApplicationContextException( "Unable to start web server" , ex) ;
}
}
在 ServletWebServerApplicationContext
的 onRefresh
方法会调用 createWebServer
方法创建嵌入式 Tomcat。
复制 private void createWebServer() {
WebServer webServer = this . webServer ;
ServletContext servletContext = getServletContext() ;
// 如果WebServer和ServletContext都为null时,证明需要创建嵌入式Tomcat
if (webServer == null && servletContext == null ) {
// 创建了嵌入式Tomcat的工厂
ServletWebServerFactory factory = getWebServerFactory() ;
// 创建嵌入式Tomcat
this . webServer = factory . getWebServer ( getSelfInitializer() );
}
else if (servletContext != null ) {
try {
getSelfInitializer() . onStartup (servletContext);
}
catch ( ServletException ex) {
throw new ApplicationContextException( "Cannot initialize servlet context" , ex) ;
}
}
initPropertySources() ;
}
这部分源码我们之前也都看过,重点来看 getWebServer
中的创建嵌入式 Tomcat 的部分:
2. TomcatServletWebServerFactory#getWebServer
复制 public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol" ;
private String protocol = DEFAULT_PROTOCOL;
public WebServer getWebServer( ServletContextInitializer ... initializers ) {
Tomcat tomcat = new Tomcat() ;
// 给嵌入式Tomcat创建一个临时文件夹,用于存放Tomcat运行中需要的文件
File baseDir = ( this . baseDirectory != null ) ? this . baseDirectory : createTempDir( "tomcat" ) ;
tomcat . setBaseDir ( baseDir . getAbsolutePath ());
// Tomcat核心概念:Connector,默认放入的protocol为NIO模式
Connector connector = new Connector( this . protocol ) ;
// 给Service添加Connector
tomcat . getService () . addConnector (connector);
// 执行定制器,修改即将设置到Tomcat中的Connector
customizeConnector(connector) ;
tomcat . setConnector (connector);
// 关闭热部署(嵌入式Tomcat不存在修改web.xml、war包等情况)
tomcat . getHost () . setAutoDeploy ( false );
// 设置backgroundProcessorDelay机制
configureEngine( tomcat . getEngine()) ;
for ( Connector additionalConnector : this . additionalTomcatConnectors ) {
tomcat . getService () . addConnector (additionalConnector);
}
// 2.1 生成TomcatEmbeddedContext
prepareContext( tomcat . getHost() , initializers) ;
// 3. 创建TomcatWebServer
return getTomcatWebServer(tomcat) ;
}
咱来看这个方法,首先它new了一个 Tomcat
对象(它还不是真正的嵌入式 Tomcat),之后下面的一大堆都是对 Tomcat 的配置,这里面着重看一眼这个最复杂的 prepareContext
方法:
2.1 prepareContext
复制 protected void prepareContext( Host host , ServletContextInitializer [] initializers) {
File documentRoot = getValidDocumentRoot() ;
// 创建TomcatEmbeddedContext
TomcatEmbeddedContext context = new TomcatEmbeddedContext() ;
if (documentRoot != null ) {
context . setResources ( new LoaderHidingResourceRoot(context) );
}
context . setName ( getContextPath() );
context . setDisplayName ( getDisplayName() );
// 设置contextPath,很熟悉了
context . setPath ( getContextPath() );
// 给嵌入式Tomcat创建docbase的临时文件夹
File docBase = (documentRoot != null ) ? documentRoot : createTempDir( "tomcat-docbase" ) ;
context . setDocBase ( docBase . getAbsolutePath ());
// 注册监听器
context . addLifecycleListener ( new FixContextListener() );
context . setParentClassLoader (( this . resourceLoader != null ) ? this . resourceLoader . getClassLoader ()
: ClassUtils . getDefaultClassLoader ());
// 设置默认编码映射
resetDefaultLocaleMapping(context) ;
addLocaleMappings(context) ;
context . setUseRelativeRedirects ( false );
try {
context . setCreateUploadTargets ( true );
}
catch ( NoSuchMethodError ex) {
// Tomcat is < 8.5.39. Continue.
}
configureTldSkipPatterns(context) ;
// 自定义的类加载器,可以加载web应用的jar包
WebappLoader loader = new WebappLoader( context . getParentClassLoader()) ;
loader . setLoaderClass ( TomcatEmbeddedWebappClassLoader . class . getName ());
// 指定类加载器遵循双亲委派机制
loader . setDelegate ( true );
context . setLoader (loader);
// 注册默认的Servlet
if ( isRegisterDefaultServlet() ) {
addDefaultServlet(context) ;
}
// 如果需要jsp支持,注册jsp的Servlet和Initializer
if ( shouldRegisterJspServlet() ) {
addJspServlet(context) ;
addJasperInitializer(context) ;
}
// 注册监听器
context . addLifecycleListener ( new StaticResourceConfigurer(context) );
ServletContextInitializer [] initializersToUse = mergeInitializers(initializers) ;
host . addChild (context);
configureContext(context , initializersToUse) ;
postProcessContext(context) ;
}
回到 getWebServer
中,最下面执行最核心的方法: getTomcatWebServer
3. getTomcatWebServer
复制 protected TomcatWebServer getTomcatWebServer( Tomcat tomcat) {
return new TomcatWebServer(tomcat , getPort() >= 0 ) ;
}
它只是new了一个 TomcatWebServer
而已,进入构造方法中:
复制 public TomcatWebServer( Tomcat tomcat , boolean autoStart) {
Assert . notNull (tomcat , "Tomcat Server must not be null" );
this . tomcat = tomcat;
this . autoStart = autoStart;
initialize() ;
}
构造方法中竟然还暗藏玄机,它在属性赋值后执行了 initialize
方法。
4. initialize
复制 private void initialize() throws WebServerException {
logger . info ( "Tomcat initialized with port(s): " + getPortsDescription( false ) );
synchronized ( this . monitor ) {
try {
// 4.1 设置Engine的id
addInstanceIdToEngineName() ;
// 4.2 获取第一个Context
Context context = findContext() ;
// 4.3 添加监听器
context . addLifecycleListener ((event) -> {
if ( context . equals ( event . getSource ()) && Lifecycle . START_EVENT . equals ( event . getType ())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors() ;
}
});
// Start the server to trigger initialization listeners
// 4.4 启动Tomcat
this . tomcat . start ();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions() ;
// ......
}
}
把整个启动过程分为几步来看:
4.1 addInstanceIdToEngineName:设置Engine的id
复制 private void addInstanceIdToEngineName() {
int instanceId = containerCounter . incrementAndGet ();
if (instanceId > 0 ) {
Engine engine = this . tomcat . getEngine ();
engine . setName ( engine . getName () + "-" + instanceId);
}
}
这部分在初始化时,containerCounter
的值是-1,调用 incrementAndGet
方法后返回0。因为是0,下面的if也就不进入了。
4.2 findContext:获取第一个Context
复制 private Context findContext() {
for ( Container child : this . tomcat . getHost () . findChildren ()) {
if (child instanceof Context) {
return (Context) child;
}
}
throw new IllegalStateException( "The host does not contain a Context" ) ;
}
这一步它要拿到Tomcat中的 host
,来获取它的 children
。通过Debug发现已经存在一组 Container
了,当然也只有一个:
拿到之后,返回去。
4.3 addLifecycleListener:添加监听器
复制 context . addLifecycleListener ((event) -> {
if ( context . equals ( event . getSource ()) && Lifecycle . START_EVENT . equals ( event . getType ())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
// 删除ServiceConnectors,以便在启动服务时不会发生协议绑定。
removeServiceConnectors() ;
}
});
这一步的单行注释是解释不让 Connector 初始化,可为什么人家组件都初始化了,就单单不让它初始化呢?这要回归到IOC容器的启动原理中。
创建嵌入式 Tomcat 的时机是 onRefresh
方法,此时还有很多单实例Bean没有被创建,此时如果直接初始化所有组件后,Connector 也被初始化,此时客户端就可以与 Tomcat 进行交互,但这个时候单实例Bean还没有初始化完毕(尤其是 DispatcherServlet
),就会导致传入的请求 Tomcat 无法处理,出现异常。
所以 SpringBoot 为了避免这个问题,会在嵌入式 Tomcat 发布事件时检测此时的 **Context**
状态是否为 **"START_EVENT"**
,如果是则将这些 **Connector**
先移除掉 。
4.4 this.tomcat.start:启动Tomcat
复制 public void start() throws LifecycleException {
getServer() ;
// 4.5
server . start ();
}
第一步的操作跟前面是一样的,下面是 server.start()
,它来真正启动嵌入式 Tomcat 。
4.5 server.start
来到 LifecycleBase
:
复制 public final synchronized void start() throws LifecycleException {
if ( LifecycleState . STARTING_PREP . equals (state) || LifecycleState . STARTING . equals (state) ||
LifecycleState . STARTED . equals (state)) {
if ( log . isDebugEnabled ()) {
Exception e = new LifecycleException() ;
log . debug ( sm . getString ( "lifecycleBase.alreadyStarted" , toString() ) , e);
} else if ( log . isInfoEnabled ()) {
log . info ( sm . getString ( "lifecycleBase.alreadyStarted" , toString() ));
}
return ;
}
if ( state . equals ( LifecycleState . NEW )) {
init() ;
} else if ( state . equals ( LifecycleState . FAILED )) {
// ......
}
这里启动时会根据当前的状态来走不同的分支,而刚启动的 Tomcat 状态为 "NEW"
,进入 init 方法:
4.5.1 StandardServer#init
来到父类 LifecycleBase
中(StandardServer
没有重写):
复制 public final synchronized void init() throws LifecycleException {
if ( ! state . equals ( LifecycleState . NEW )) {
invalidTransition( Lifecycle . BEFORE_INIT_EVENT ) ;
}
try {
setStateInternal( LifecycleState . INITIALIZING , null , false ) ;
initInternal() ;
setStateInternal( LifecycleState . INITIALIZED , null , false ) ;
} catch ( Throwable t) {
handleSubClassException(t , "lifecycleBase.initFail" , toString()) ;
}
}
上面对于不为NEW的状态,会额外执行方法,当前状态为NEW ,不进入,走下面的try块。try块在 initInternal
的前后设置了两次生命周期的状态(初始化中、初始化完成),说明 initInternal
方法中一定是真正的 Tomcat 组件初始化的过程。
4.5.2 StandardServer#initInternal
来到 StandardServer
:
复制 protected void initInternal() throws LifecycleException {
super . initInternal ();
// ......
}
先来到父类 LifecycleBase
的 initInternal
方法:
复制 protected void initInternal () throws LifecycleException {
// If oname is not null then registration has already happened via preRegister().
// 如果oname不为null,则已经通过preRegister()进行了注册
if (oname == null ) {
mserver = Registry . getRegistry ( null , null ). getMBeanServer ();
oname = register ( this , getObjectNameKeyProperties ());
}
}
默认情况下 oname
为null,走下面的初始化过程。初始化完成后的效果:
回到子类 StandardServer
:
复制 protected void initInternal() throws LifecycleException {
super . initInternal ();
// ......
// Initialize our defined Services
for ( int i = 0 ; i < services . length ; i ++ ) {
services[i] . init ();
}
}
中间的一大段我们暂且不关心,主要来看最后一步:它要初始化这些 Server
中的 Service
。
4.5.3 又回到LifecycleBase
复制 public final synchronized void init() throws LifecycleException {
if ( ! state . equals ( LifecycleState . NEW )) {
invalidTransition( Lifecycle . BEFORE_INIT_EVENT ) ;
}
try {
setStateInternal( LifecycleState . INITIALIZING , null , false ) ;
initInternal() ;
setStateInternal( LifecycleState . INITIALIZED , null , false ) ;
} catch ( Throwable t) {
handleSubClassException(t , "lifecycleBase.initFail" , toString()) ;
}
}
再走一遍流程,进到 initInternal
方法,这次初始化的是 StandardService
:
4.5.4 StandardService#initInternal
复制 protected void initInternal() throws LifecycleException {
super . initInternal ();
if (engine != null ) {
engine . init ();
}
// Initialize any Executors
for ( Executor executor : findExecutors() ) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor) . setDomain ( getDomain() );
}
executor . init ();
}
// Initialize mapper listener
mapperListener . init ();
// Initialize our defined Connectors
synchronized (connectorsLock) {
for ( Connector connector : connectors) {
connector . init ();
}
}
}
发现了这里面要依次初始化几个组件:Engine
,Executor
,LifecycleMBeanBase(mapperListener)
,Connector
。
到这里咱应该有种意识:这几个家伙的初始化不会又回到 LifecycleBase
中了吧?可以很确定的回答:基本是的。。。所以接下来咱就不贴 LifecycleBase
的源码了,直接看这几个组件的实现吧:
4.5.5 StandardEngine#initInternal
复制 protected void initInternal() throws LifecycleException {
// Ensure that a Realm is present before any attempt is made to start
// one. This will create the default NullRealm if necessary.
// 在尝试启动一个Realm之前,请确保存在一个Realm。如有必要,这将创建默认的NullRealm
getRealm() ;
super . initInternal ();
}
public Realm getRealm() {
Realm configured = super . getRealm ();
// If no set realm has been called - default to NullRealm
// This can be overridden at engine, context and host level
if (configured == null ) {
configured = new NullRealm() ;
this . setRealm (configured);
}
return configured;
}
很明显这里是初始化Realm的,且实现很简单,不再展开。
4.5.6 Executor#initInternal
复制 protected void initInternal() throws LifecycleException {
super . initInternal ();
}
它还是调的父类 LifecycleMBeanBase
的方法,不再赘述。
4.5.7 MapperListener#initInternal
MapperListener
没有重写 initInternal
方法,相当于也跟上面一样,不再赘述。
复制 protected void initInternal() throws LifecycleException {
// If oname is not null then registration has already happened via preRegister().
// 如果oname不为null,则已经通过preRegister()进行了注册
if (oname == null ) {
mserver = Registry . getRegistry ( null , null ) . getMBeanServer ();
oname = register( this , getObjectNameKeyProperties()) ;
}
}
跟上面一样,不再赘述。
4.5.8 Connector#initInternal
复制 protected void initInternal() throws LifecycleException {
super . initInternal ();
// ......
// Initialize adapter
// CoyoteAdapter负责连接Coyote和Servlet容器(web应用)
adapter = new CoyoteAdapter( this ) ;
protocolHandler . setAdapter (adapter);
// ......
try {
protocolHandler . init ();
} catch ( Exception e) {
throw new LifecycleException(
sm . getString( "coyoteConnector.protocolHandlerInitializationFailed" ) , e) ;
}
}
中间部分它初始化了一个 CoyoteAdapter
,它负责连接 Connector
和 Container
,也就是 Coyote
和 Servlet容器
。
最下面的try-catch中,它又调用了 protocolHandler.init
:
4.5.9 protocolHandler.init
来到 AbstractHttp11Protocol
:
复制 public void init() throws Exception {
// Upgrade protocols have to be configured first since the endpoint
// init (triggered via super.init() below) uses this list to configure
// the list of ALPN protocols to advertise
// 必须先配置升级协议,因为端点初始化(通过下面的super.init()触发)使用此列表来配置要发布的ALPN协议列表
for ( UpgradeProtocol upgradeProtocol : upgradeProtocols) {
configureUpgradeProtocol(upgradeProtocol) ;
}
super . init ();
}
Debug发现这个 upgradeProtocols
为空,直接走下面父类(AbstractProtocol
)的 init
方法:
复制 public void init() throws Exception {
if ( getLog() . isInfoEnabled ()) {
getLog() . info ( sm . getString ( "abstractProtocolHandler.init" , getName() ));
logPortOffset() ;
}
if (oname == null ) {
// Component not pre-registered so register it
oname = createObjectName() ;
if (oname != null ) {
Registry . getRegistry ( null , null ) . registerComponent ( this , oname , null );
}
}
if ( this . domain != null ) {
rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName()) ;
Registry . getRegistry ( null , null ) . registerComponent (
getHandler() . getGlobal () , rgOname , null );
}
String endpointName = getName() ;
endpoint . setName ( endpointName . substring ( 1 , endpointName . length () - 1 ));
endpoint . setDomain (domain);
endpoint . init ();
}
上面又是一堆初始化,这个咱暂且不关注,注意最底下有一个 endpoint.init
:
4.5.10 endpoint.init
来到 AbstractEndPoint
:
复制 public final void init() throws Exception {
// Debug为false
if (bindOnInit) {
bindWithCleanup() ;
bindState = BindState . BOUND_ON_INIT ;
}
if ( this . domain != null ) {
// Register endpoint (as ThreadPool - historical name)
oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"" ) ;
Registry . getRegistry ( null , null ) . registerComponent ( this , oname , null );
ObjectName socketPropertiesOname = new ObjectName(domain +
":type=ThreadPool,name=\"" + getName() + "\",subType=SocketProperties" ) ;
socketProperties . setObjectName (socketPropertiesOname);
Registry . getRegistry ( null , null ) . registerComponent (socketProperties , socketPropertiesOname , null );
for ( SSLHostConfig sslHostConfig : findSslHostConfigs() ) {
registerJmx(sslHostConfig) ;
}
}
}
这里面又是初始化 oname
,又是配置 socketProperties
的,但这里面再也没见到 init
方法,证明这部分初始化过程已经结束了。
值得注意的是,Debug发现 bindOnInit
变量为false,说明嵌入式 Tomcat 不在初始化期间绑定端口号 。
4.5.11 初始化小结
嵌入式 Tomcat 的组件初始化步骤顺序如下:
至此,初始化过程完毕,回到 start
方法中:
复制 public final synchronized void start() throws LifecycleException {
// ......
if ( state . equals ( LifecycleState . NEW )) {
init() ;
} // else if ......
try {
setStateInternal( LifecycleState . STARTING_PREP , null , false ) ;
startInternal() ;
// ......
}
接下来到了真正启动的部分了:startInternal
4.6 StandardServer#startInternal
复制 protected void startInternal() throws LifecycleException {
// 发布启动事件
fireLifecycleEvent(CONFIGURE_START_EVENT , null ) ;
setState( LifecycleState . STARTING ) ;
// 4.6.1 NamingResources启动
globalNamingResources . start ();
// Start our defined Services
synchronized (servicesLock) {
for ( int i = 0 ; i < services . length ; i ++ ) {
// 4.6.2 Service启动
services[i] . start ();
}
}
if (periodicEventDelay > 0 ) {
monitorFuture = getUtilityExecutor() . scheduleWithFixedDelay (
new Runnable() {
@ Override
public void run () {
startPeriodicLifecycleEvent() ;
}
} , 0 , 60 , TimeUnit . SECONDS );
}
}
startInternal
方法中有两部分启动:globalNamingResources
启动,services
启动。分别来看:
4.6.1 globalNamingResources.start
来到 NamingResourcesImpl
,因为它也实现了 LifecycleBase
,还是会来到上面的 startInternal
方法中:
复制 protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT , null ) ;
setState( LifecycleState . STARTING ) ;
}
这部分只是发布事件和设置状态而已,与之前一致,不再赘述。
4.6.2 StandardService#start
最终又来到 startInternal
方法了:
复制 protected void startInternal() throws LifecycleException {
if ( log . isInfoEnabled ())
log . info ( sm . getString ( "standardService.start.name" , this . name ));
setState( LifecycleState . STARTING ) ;
// Start our defined Container first
if (engine != null ) {
synchronized (engine) {
// 4.6.3
engine . start ();
}
}
synchronized (executors) {
for ( Executor executor : executors) {
// 4.6.6
executor . start ();
}
}
// 4.6.7
mapperListener . start ();
// Start our defined Connectors second
synchronized (connectorsLock) {
for ( Connector connector : connectors) {
// If it has already failed, don't try and start it
if ( connector . getState () != LifecycleState . FAILED ) {
// 4.6.8
connector . start ();
}
}
}
}
发现这部分与之前的初始化几乎一致!也是依次启动 Engine
、Executor
、MapperListener
、Connector
。
4.6.3 Engine#start
复制 protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if ( log . isInfoEnabled ()) {
log . info ( sm . getString ( "standardEngine.start" , ServerInfo . getServerInfo ()));
}
// Standard container startup
super . startInternal ();
}
它直接调的父类 ContainerBase
的 startInternal
方法:
复制 protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
logger = null ;
getLogger() ;
// Cluster与集群相关,SpringBoot项目中使用嵌入式Tomcat,不存在集群
Cluster cluster = getClusterInternal() ;
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster) . start ();
}
// Realm与授权相关
Realm realm = getRealmInternal() ;
if (realm instanceof Lifecycle) {
((Lifecycle) realm) . start ();
}
// Start our child containers, if any
// Container的类型是StandardHost
Container children[] = findChildren() ;
List < Future < Void >> results = new ArrayList <>();
for ( int i = 0 ; i < children . length ; i ++ ) {
// 4.6.4 异步初始化Host
results . add ( startStopExecutor . submit ( new StartChild(children[i]) ));
}
MultiThrowable multiThrowable = null ;
for ( Future < Void > result : results) {
try {
result . get ();
} catch ( Throwable e) {
log . error ( sm . getString ( "containerBase.threadedStartFailed" ) , e);
if (multiThrowable == null ) {
multiThrowable = new MultiThrowable() ;
}
multiThrowable . add (e);
}
}
if (multiThrowable != null ) {
throw new LifecycleException( sm . getString( "containerBase.threadedStartFailed" ) ,
multiThrowable . getThrowable()) ;
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline) . start ();
}
setState( LifecycleState . STARTING ) ;
// Start our thread
if (backgroundProcessorDelay > 0 ) {
monitorFuture = Container . getService ( ContainerBase . this ) . getServer ()
. getUtilityExecutor () . scheduleWithFixedDelay (
new ContainerBackgroundProcessorMonitor() , 0 , 60 , TimeUnit . SECONDS );
}
}
这里面又是嵌套的初始化了其他组件,一一来看:
4.6.4 new StartChild(children[i])
这部分比较有意思,它用了异步初始化,先来看看 StartChild
的定义:
复制 private static class StartChild implements Callable < Void >
它实现了带返回值的异步多线程接口 **Callable**
!那里面的核心方法就是 **call**
:
复制 public Void call() throws LifecycleException {
child . start ();
return null ;
}
它在这里初始化 child
,而通过Debug得知 child
的类型是 StandardHost
,故来到 StandardHost
的 start
方法:
复制 protected synchronized void startInternal() throws LifecycleException {
// Set error report valve
String errorValve = getErrorReportValveClass() ;
if ((errorValve != null ) && ( ! errorValve . equals ( "" ))) {
try {
boolean found = false ;
Valve [] valves = getPipeline() . getValves ();
for ( Valve valve : valves) {
if ( errorValve . equals ( valve . getClass () . getName ())) {
found = true ;
break ;
}
}
if ( ! found) {
Valve valve =
(Valve) Class . forName (errorValve) . getConstructor () . newInstance ();
getPipeline() . addValve (valve);
}
} catch ( Throwable t) {
ExceptionUtils . handleThrowable (t);
log . error ( sm . getString (
"standardHost.invalidErrorReportValveClass" ,
errorValve) , t);
}
}
super . startInternal ();
}
上面的一个大if结构是设置错误提示页面的,下面又调父类的 startInternal
:
复制 protected synchronized void startInternal() throws LifecycleException {
// ......
// Start our child containers, if any
Container children[] = findChildren() ;
List < Future < Void >> results = new ArrayList <>();
for ( int i = 0 ; i < children . length ; i ++ ) {
results . add ( startStopExecutor . submit ( new StartChild(children[i]) ));
}
又回来了。。。因为一个 Host
包含一个 Context
。
Host
搜索children就会搜到它下面的 Context
,之后又是下面的初始化过程,进入 Context 的初始化:
4.6.5 TomcatEmbeddedContext#start
这个方法非常非常长(300行+),小册就不全部贴出来了,咱现在是启动阶段,那我们只关心里面的 start 相关代码。
借由前面的Debug过程,我们先总结一个规律:所有带生命周期性质的组件,都会在启动时走到 **startInternal**
方法 。
那我们接下来Debug这个300行+的代码时,只需要在方法头和方法尾打上断点,中间部分只要走 startInternal
方法的,就是组件的初始化。通过Debug,发现有如下组件被调用了 start 方法:
组件的 startInternal
方法就不一一列举了,小册把这些组件的核心功能列举一下,有兴趣的小伙伴可以深入研究一下,小册也只是引导小伙伴们对嵌入式 Tomcat 的底层原理有一个大概的认识和了解。
4.6.6 Executor#start
复制 synchronized (executors) {
for ( Executor executor : executors) {
// 4.6.6
executor . start ();
}
}
Engine
启动完成后,下一步到了 Executor
的启动。但由于 Executor
没有实现 startInternal
方法,故这一步不再展开。
4.6.7 MapperListener#start
复制 mapperListener . start ();
Executor
启动完成后,接下来启动 MapperListener
:
复制 public void startInternal() throws LifecycleException {
setState( LifecycleState . STARTING ) ;
Engine engine = service . getContainer ();
if (engine == null ) {
return ;
}
// 获取当前部署的主机名(本地调试为localhost)
findDefaultHost() ;
// 4.6.7.1 把当前自身注册到Engine、Host、Context、Wrapper中
addListeners(engine) ;
// 取出的Container的类型为Host
Container [] conHosts = engine . findChildren ();
for ( Container conHost : conHosts) {
Host host = (Host) conHost;
if ( ! LifecycleState . NEW . equals ( host . getState ())) {
// Registering the host will register the context and wrappers
// 4.6.7.2 将Host、Context、Wrapper注册到当前监听器中
registerHost(host) ;
}
}
}
这里面它干了三件事请:获取主机名,将监听器注册到各组件中,将各组件注册到监听器(实现双向)。咱主要看一眼两方互相注册的动作:
4.6.7.1 addListeners
复制 private void addListeners( Container container) {
container . addContainerListener ( this );
container . addLifecycleListener ( this );
for ( Container child : container . findChildren ()) {
addListeners(child) ;
}
}
很明显这是递归调用,而且从 Engine
开始一层一层往下执行,都把当前监听器注册进去。
4.6.7.2 registerHost
复制 private void registerHost( Host host) {
String [] aliases = host . findAliases ();
mapper . addHost ( host . getName () , aliases , host);
for ( Container container : host . findChildren ()) {
if ( container . getState () . isAvailable ()) {
registerContext((Context) container) ;
}
}
// Default host may have changed
findDefaultHost() ;
// log ......
}
注意这里面的for循环,又调了 registerContext
,可见这个思路也跟上面差不多,类似于递归,不过这是手动一步一步往里设置(毕竟类型不一样)。
4.6.8 Connector#start
复制 // Start our defined Connectors second
synchronized (connectorsLock) {
for ( Connector connector : connectors) {
// If it has already failed, don't try and start it
if ( connector . getState () != LifecycleState . FAILED ) {
connector . start ();
}
}
}
最后一步是启动 Connector
。但通过Debug发现根本没有 Connector
!
为什么之前还看到一个 Connector
,现在就没了呢?还记得在 this.tomcat.start();
之前有一个监听器吗?
复制 context . addLifecycleListener ((event) -> {
if ( context . equals ( event . getSource ()) && Lifecycle . START_EVENT . equals ( event . getType ())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors() ;
}
});
很明显就是这一步把 Connector
删了嘛!
Remove service connectors so that protocol binding doesn't happen when the service is started.
删除 Service
的 Connector
,以便在启动服务时不会发生协议绑定。
那大概率就是这一步让这个家伙给删了。注意这个监听器是在 context
中注册的,那我们猜想,应该是 Context
启动时触发的这个监听效果。
回过头来重新Debug一次,发现在 Engine
启动之后,Connector
就已经没了。
将断点打在上面 context
的监听器上,放行,发现果然 在 **TomcatEmbeddedContext**
的启动期间触发了这个监听器 。
至于为什么要删除的原因,上面4.3章节也描述了,是为了防止 SpringBoot 应用还没有初始化完成时就已经可以接收客户端的请求 。
至此,tomcat.start();
方法彻底执行完成。
4.6.9 启动小结
启动过程依次启动了如下组件:
4.7 回到initialize
复制 private void initialize() throws WebServerException {
// ......
this . tomcat . start ();
// We can re-throw failure exception directly in the main thread
// 如果上面的启动出现问题,则抛出异常
rethrowDeferredStartupExceptions() ;
try {
// 将当前Context与当前ClassLoader绑定
ContextBindings . bindClassLoader (context , context . getNamingToken () , getClass() . getClassLoader ());
}
catch ( NamingException ex) {
// Naming is not enabled. Continue
}
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
// 4.8 阻止Tomcat结束
startDaemonAwaitThread() ;
}
catch ( Exception ex) {
stopSilently() ;
destroySilently() ;
throw new WebServerException( "Unable to start embedded Tomcat" , ex) ;
}
}
}
嵌入式 Tomcat 启动完成后,在try块的最底下会起一个新的线程,阻止 Tomcat 结束。
4.8 startDaemonAwaitThread
复制 private void startDaemonAwaitThread() {
Thread awaitThread = new Thread( "container-" + ( containerCounter . get())) {
@ Override
public void run () {
TomcatWebServer . this . tomcat . getServer () . await ();
}
};
awaitThread . setContextClassLoader ( getClass() . getClassLoader ());
awaitThread . setDaemon ( false );
awaitThread . start ();
}
这里面它会起一个新的 awaitThread
线程,并回调 Tomcat 中 Server
的 await
方法,并且它还设置 Daemon
为false。
先解释一下为什么设置 Daemon
:Tomcat 中所有的进程都是 Daemon 线程,在Java应用中,只要有一个非 Daemon 线程还在运行,则 Daemon 线程就不会停止,整个应用也不会终止 。既然要让 Tomcat 一直运行以监听客户端请求,就必须需要让 Tomcat 内部的 Daemon 线程都存活,根据前面的描述,就必须制造一个能卡住停止的非 Daemon 线程。于是上面新起的 awaitThread
线程就被设置为非 Daemon 线程。
下面看看线程中执行的 await
方法:(源码很长,只截取出跟 SpringBoot 嵌入式 Tomcat 有关的部分)
复制 public void await() {
// Negative values - don't wait on port - tomcat is embedded or we just don't like ports
// 如果关闭Tomcat的端口是-2,则直接返回,不卡线程
if ( getPortWithOffset() == - 2 ) {
// undocumented yet - for embedding apps that are around, alive.
return ;
}
// 如果关闭Tomcat的端口是-1,代表是嵌入式Tomcat
if ( getPortWithOffset() == - 1 ) {
try {
awaitThread = Thread . currentThread ();
while ( ! stopAwait) {
try {
Thread . sleep ( 10000 );
} catch ( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null ;
}
return ;
}
// Set up a server socket to wait on
// 退出端口正常的处理(属于外部Tomcat逻辑)......
}
可以发现,如果设置的 Tomcat 的退出端口是 -1,则代表是嵌入式 Tomcat,它会每10秒会检查一次stopAwait的值 ,如果为true则停止卡线程,让 Tomcat 停止。
默认请款下 Tomcat 的退出端口是8005,为什么这里会变成 -1 呢?追踪源码,发现在 Tomcat
的 getServer
方法中有设置:
复制 public Server getServer() {
if (server != null ) {
return server;
}
System . setProperty ( "catalina.useNaming" , "false" );
server = new StandardServer() ;
initBaseDir() ;
// Set configuration source
ConfigFileLoader . setSource ( new CatalinaBaseConfigurationSource( new File(basedir) , null ) );
// 设置端口号为 -1,代表嵌入式
server . setPort ( - 1 );
Service service = new StandardService() ;
service . setName ( "Tomcat" );
server . addService (service);
return server;
}
至此,嵌入式 Tomcat 已经成功创建好,但 Connector
还没有归还,还在被删除中。
5. ServletWebServerApplicationContext#startWebServer
当IOC容器的 onRefresh 方法执行完,单实例Bean初始化完成后,来到 finishRefresh
方法:
复制 protected void finishRefresh() {
super . finishRefresh ();
WebServer webServer = startWebServer() ;
if (webServer != null ) {
publishEvent( new ServletWebServerInitializedEvent(webServer , this )) ;
}
}
在这里它会真正启动嵌入式 Tomcat 容器:
复制 private WebServer startWebServer() {
WebServer webServer = this . webServer ;
if (webServer != null ) {
webServer . start ();
}
return webServer;
}
可以看到它在这里调用了 TomcatWebServer
的 start
方法。
6. TomcatWebServer#start
复制 public void start() throws WebServerException {
synchronized ( this . monitor ) {
if ( this . started ) {
return ;
}
try {
// 6.1 还原、启动Connector
addPreviouslyRemovedConnectors() ;
// 只拿一个Connector
Connector connector = this . tomcat . getConnector ();
if (connector != null && this . autoStart ) {
// 6.2 延迟启动
performDeferredLoadOnStartup() ;
}
// 检查Connector是否正常启动
checkThatConnectorsHaveStarted() ;
this . started = true ;
logger . info ( "Tomcat started on port(s): " + getPortsDescription( true ) + " with context path '"
+ getContextPath() + "'" );
}
// catch ......
finally {
// 解除ClassLoader与TomcatEmbeddedContext的绑定关系
Context context = findContext() ;
ContextBindings . unbindClassLoader (context , context . getNamingToken () , getClass() . getClassLoader ());
}
}
}
源码中的注释已解释的比较清楚,下面分述源码中两个重要的环节:还原 Connector
和启动 Connector
:
6.1 addPreviouslyRemovedConnectors:还原Connector
复制 private void addPreviouslyRemovedConnectors() {
Service[] services = this.tomcat.getServer().findServices();
for (Service service : services) {
Connector[] connectors = this.serviceConnectors.get(service);
if (connectors != null) {
for (Connector connector : connectors) {
// 6.1.1 添加并启动
service.addConnector(connector);
if (!this.autoStart) {
stopProtocolHandler(connector);
}
}
this.serviceConnectors.remove(service);
}
}
}
可以发现它将一个缓存区的 Connector
一个一个取出放入 Service
中。注意在 service.addConnector
中有顺便启动的部分:
6.1.1 service.addConnector
复制 public void addConnector(Connector connector) {
synchronized (connectorsLock) {
connector.setService(this);
Connector results[] = new Connector[connectors.length + 1];
System.arraycopy(connectors, 0, results, 0, connectors.length);
results[connectors.length] = connector;
connectors = results;
}
try {
if (getState().isAvailable()) {
// 6.1.2 启动Connector
connector.start();
}
} catch (LifecycleException e) {
throw new IllegalArgumentException(
sm.getString("standardService.connector.startFailed", connector), e);
}
// Report this property change to interested listeners
support.firePropertyChange("connector", null, connector);
}
前面的部分是取出 Connector
,并与 Service
绑定,之后中间部分的try块,会启动 Connector
:
6.1.2 connector.start
复制 protected void startInternal() throws LifecycleException {
// Validate settings before starting
if (getPortWithOffset() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset())));
}
setState(LifecycleState.STARTING);
try {
// 启动ProtocolHandler
protocolHandler.start();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
}
}
Connector
的启动会引发 ProtocolHandler
的启动:
6.1.3 protocolHandler.start
复制 public void start() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
logPortOffset();
}
// 启动EndPoint
endpoint.start();
monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
new Runnable() {
@Override
public void run() {
if (!isPaused()) {
startAsyncTimeout();
}
}
}, 0, 60, TimeUnit.SECONDS);
}
ProtocolHandler
的启动会引发 EndPoint 的启动,至此所有组件均已启动完毕。
6.2 performDeferredLoadOnStartup:延迟启动
复制 private void performDeferredLoadOnStartup() {
try {
for (Container child : this.tomcat.getHost().findChildren()) {
if (child instanceof TomcatEmbeddedContext) {
// 延迟启动Context
((TomcatEmbeddedContext) child).deferredLoadOnStartup();
}
}
}
catch (Exception ex) {
if (ex instanceof WebServerException) {
throw (WebServerException) ex;
}
throw new WebServerException("Unable to start embedded Tomcat connectors", ex);
}
}
发现这里面会延迟启动 TomcatEmbeddedContext
,此处它对比较老的表现层框架(如Struts)做了一些兼容支持,主要是替换类加载器,由于 SpringBoot 默认使用WebMvc或WebFlux,已不采用过老的表现层框架,故此处不再展开讨论。
至此,嵌入式 Tomcat 完整启动。
小结
嵌入式 Tomcat 与外置的 Tomcat 在核心组件上都是一样的,主要包括 Service
、Connector
、Engine
、Host
、Context
。
Tomcat 的启动过程分为初始化和启动两个步骤,分别按照核心组件的顺序启动。
嵌入式 Tomcat 在启动时要先移除掉 Connector
,防止IOC容器还没有全部启动完成后就能接收客户端的请求。