1.概述
要想了解Tomcat的啟動流程,必須先弄明白Tomcat有哪些組件,而對于Tomcat組件的層級結構了解,我們必須弄明白Tomcat一個最重要的組態檔“server.xml”,如果有過Tomcat調優經驗或者對Tomat有一定了解的話,一定知道這個檔案,他位于${tomcat.base}/conf/server.xml,通過這個檔案,我們可以配給幾乎所有tomcat的引數資訊(當然不會包括jvm相關的引數),那么我們先看一下server.xml的真實面目,為了更清楚的展現Tomcat組件,我把server.xml中的注釋部分全部洗掉了,
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
<Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
<Context docBase="webapps/test" path="/test" reloadable="true" />
</Host>
</Engine>
</Service>
</Server>
需要指出的是,server.xml組態檔中并沒有<Context>元素標簽,不是說這里不可以用這個標簽,而是Tomcat的官方檔案并不提議通過這種方式設定應用程式的位置,在http://ip:8080/docs/config/context.html中有如下一段話:

這段話的開頭就說明 了并不建議直接在server.xml檔案中添加<Context>元素,當然,感興趣的可以往后面看看原因,說了這么多,看到上面的按個xml其實還是有些犯怵,這么多,目錄結構哪有那么清晰,為了方便大家掌握,我有做了一個目錄結構圖:

在上圖中,Protocol Handler并不是server.xml的一個元素,但是他通過<Connector>中的protocol屬性配置的,默認HTTP/1.1,然后針對不同版本的tomat(比如tomcat7和tomcatd8),默認的協議處理器是不一樣的,下方代碼塊是取自tomcat8.5.59,
/**
* Tomcat8.5.59
*org.apache.catalina.connector.Connector#setProtocol
*/
public void setProtocol(String protocol) {
boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
AprLifecycleListener.getUseAprConnector();
if ("HTTP/1.1".equals(protocol) || protocol == null) {
if (aprConnector) {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
} else {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
}
} else if ("AJP/1.3".equals(protocol)) {
if (aprConnector) {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
} else {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
}
} else {
//決定可以直接輸入類名
setProtocolHandlerClassName(protocol);
}
}
如下代碼塊取自Tomcat7..0.103,
/**
* Tomcat7.0.103
*org.apache.catalina.connector.Connector#setProtocol
*/
public void setProtocol(String protocol) {
if (AprLifecycleListener.isAprAvailable()) {
if ("HTTP/1.1".equals(protocol)) {
setProtocolHandlerClassName
("org.apache.coyote.http11.Http11AprProtocol");
} else if ("AJP/1.3".equals(protocol)) {
setProtocolHandlerClassName
("org.apache.coyote.ajp.AjpAprProtocol");
} else if (protocol != null) {
setProtocolHandlerClassName(protocol);
} else {
setProtocolHandlerClassName
("org.apache.coyote.http11.Http11AprProtocol");
}
} else {
if ("HTTP/1.1".equals(protocol)) {
setProtocolHandlerClassName
("org.apache.coyote.http11.Http11Protocol");
} else if ("AJP/1.3".equals(protocol)) {
setProtocolHandlerClassName
("org.apache.coyote.ajp.AjpProtocol");
} else if (protocol != null) {
setProtocolHandlerClassName(protocol);
}
}
}
通過上面兩個代碼塊,我們清楚的指導Tomcat8.5.59配置的類為org.apache.coyote.http11.Http11NioProtocol,Tomcat7.0.103中配置的類org.apache.coyote.http11.Http11Protocol,所以Tomcat8.5.59采用同步非阻塞的方式,而Tomcat7.0.103則采用同步阻塞的方式,
到目前為止,我們了解了Tomcat的主要組件以及層次結構,那么接下來我們就進入Tomcat的原始碼世界,一起揭開他的神秘面紗,
2. 流程時序圖
為了進一步更好的理解Tomat的啟動流程,我們先來一張時序圖,心里先有個粗略的印象,需要注意的事,要注意每一步的序號,接下來我是按照序號進行講解的,

3.啟動流程原始碼分析
通過上面的時序圖,我們可以看出來Tomcat啟動主要就是三步,Tomcat環境的初始化、Tomcat組件的各個init方法以及Tomcat組件的各個start方法,由于Tomcat的各個組件都要記錄到生命周期中,所以基本被org.apache.catalina.util. LifecycleMBeanBase的init和start方法包裹,真正的實作是以Standard開頭的類的以Internal結尾的發方法中,比如,service的init方法的具體實作為org.apache.catalina.core.StandardService#initInternal,接下里,我們就以Tomcat的入口類org.apache. catalina.startup.Bootstrap(以下簡稱"Bootstrap")作為切入點,進行一步一步的詳細分析,
3.1 Tomcat環境初始化(0:static)
通過0:static這個關鍵字,相信大家已經猜出這一部分代碼主要是在哪執行的吧?沒錯,是Tomcat啟動類Bootstrap的靜態代碼塊中,針對這段代碼塊,主要實作一個功能,初始化catalinaHomeFile和catalinaBaseFile兩個變數,便于尋找Tomcat涉及到的組態檔、classpath等路徑,針對catalinaHomeFile變數,尋找的優先級為:環境變數catalina.home->包含bootsrap.jar的父級目錄->程式運行的當前路徑System.getProperty("user.dir");針對catalinaBaseFile變數,尋找的優先級為:環境變數catalina.base->catalinaHomeFile,
3.2 Bootstap物件的初始化(1.1:init)
在方法org.apache.catalina.startup.Bootstrap#init()中,主要做了如下幾件事:
1)初始化Tomcat的三大類加載器(commonLoader->catalinaLoader->sharedLoader),
類加載的初始化主要依賴于組態檔${tomcat.base}/conf/catalina.properties中的common.loader、server.loader和shared.loader,默認情況下,common.loader會有值,commonLoader作為jvm中AppClassLoader的子加載器,主要增加加載Tomcat下的的jar或者class檔案,而server.loader和shared.loader屬性配置為空,將commonLoader作為自己的加載器,也就是說,默認情況下,AppClassLoader有個子加載器commonLoader,commonLoader有一個與其相同的子加載器catalinaLoader,catalinaLoader有一個與其相同的子加載器sharedLoader,
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
2)初始化Catalina類,
Catalina作為Tomcat副總級別的人物,對于Tomcat有著極其重要的作用,Bootstrap其實沒有做多少作業,主要的初始化作業包括server.xml檔案的決議、server組件的初始化都是在此類中進行的,為了便于擴展,此處采用了反射的方式進行初始化此類,最終,將Catalina物件賦值給catalinaDaemon ,
public void init() throws Exception {
...
//通過反射的方式初始化Catalina類,并將最底層的類加載器sharedLoader賦值給物件的父加載器
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
//Catalina作為守護執行緒
catalinaDaemon = startupInstance;
}
這里針對類加載器需要注意的事
1)Catalina并沒有使用末級加載器sharedLoader加載此類,而是使用其父加載器catalina加載,
2)Catalina有一個屬性parentClassLoader,并將shareLoader賦值給他,而這也將作為應用部署的父類加載器,
3.3 組件的初始化(1.2:load)
在Bootstrap#main中,通過呼叫Bootstrap#load方法,此方法仍然通過反射的方式呼叫Catalina的load方法,
private void load(String[] arguments) throws Exception {
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled()) {
log.debug("Calling startup class " + method);
}
method.invoke(catalinaDaemon, param);
}
這個類沒什么可說了,很容易理解,那么接下來進入我們的重頭戲,從Catalina的load方法開始,尋求Tomcat是怎么初始化他的各個組件的,
3.3.1 Catalina的加載(1.2.1:load)
Catalina加載在方法org.apache.catalina.startup.Catalina#load()中執行,在此方法中,主要完成了以下三件事:
1)決議server.xml檔案,并將server物件賦值給org.apache.catalina.startup.Catalina#server,
2)重新定向System.out流,
3)server組件的初始化
3.3.1.1 決議server.xml
server.xml決議采用SAXParser決議器加創建決議規則的方式進行決議,代碼開始通過createStartDigester()方法創建Digester,什么是Digester呢?
A Digester processes an XML input stream by matching a series of element nesting patterns to execute Rules that have been added prior to the start of parsing. This package was inspired by the XmlMapper class that was part of Tomcat 3.0 and 3.1, but is organized somewhat differently.
Digester實作了SAXParser默認的處理器DefaultHandler2,通過添加一系列規則,實作server.xml檔案的決議,而這些規則要繼承org.apache.tomcat.util.digester.Rule類,主要實作了begin(namespace,name,attributes)和end(namespace, name)方法,主要完成SAXParser在決議到當前元素開始元素標記的業務邏輯(eg:<sever>)和決議結束元素標記(eg:</sever>),在Tomcat原始碼中,主要的規則包括org.apache.tomcat.util.digester.ObjectCreateRule、org.apache.catalina.startup.ConnectorCreateRule和org.apache.catalina.startup.SetAllPropertiesRule,其中ObjectCreateRule處理除了Connector之外幾乎所有的元素,ConnectorCreateRule主要針對Connector標簽進行決議,SetAllPropertiesRule值用于將其他節點的屬性復制到當前節點相同的屬性名稱上,
digester.addRule("Server/Service/Connector",
new ConnectorCreateRule());
digester.addRule("Server/Service/Connector",
new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"}));
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector");
比如,看上面代碼,針對相同的標簽Server/Service/Connector,添加了兩個規則,
上面還說到server物件賦值給org.apache.catalina.startup.Catalina#server,那么這一步是怎么設定的呢?如果我們通過查看org.apache.catalina.startup.Catalina#setServer()被哪些物件應用,根本是查不到的,好吧,他也是通過規則設定的,
digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");
digester.addSetProperties("Server");
digester.addSetNext("Server","setServer","org.apache.catalina.Server");
3.3.1.2 重新定向System.out流
這一步不難理解,將System.out和System.err重定向到org.apache.tomcat.util.log.SystemLogHandler上,SystemLogHandler又是一個什么鬼?
/** * This helper class may be used to do sophisticated redirection of * System.out and System.err on a per Thread basis. * 可以使用該幫助程式類在每個執行緒的基礎上對System.out和System.err進行復雜的重定向, * * A stack is implemented per Thread so that nested startCapture * and stopCapture can be used. * * @author Remy Maucherat * @author Glenn L. Nielsen */
我們知道,System.out他是一個靜態的輸出流,是一個被所有執行緒共享的物件,而tomcat啟動了一大堆執行緒,為了保證縣城的安全,需要通過SystemLogHandler包裝一下,在這個類中,通過ThreadLocal(執行緒區域變數)保證了各個執行緒System.out的獨立性,
3.3.1.3 server組件的初始化
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
}
server初始化放在下一章節講解,這里只需明白呼叫的server的init()方法即可,
3.3.2 server初始化(1.2.2:init)
在這個方法中,并沒有做太多實質性的作業,主要是通過回圈遍歷services實作service的初始化,
// Initialize our defined Services
for (Service service : services) {
service.init();
}
3.3.3 service初始化(1.2.3:init)
由于Tomcat的各個組件都有生命周期的概念,其無論是容器類組件還是非容器類組件都繼承了org.apache.catalina.util. LifecycleBase,而LifecycleBase又實作了org.apache.catalina.Lifecycle,對于所有繼承LifecycleBase的組件,通過呼叫org.apache.catalina.util.LifecycleBase#init方法,改變當前組件的狀態,而真實的實作方法為org.apache.catalina.util. LifecycleBase#initInternal(內部的初始化方法),通過下面代碼可以知道,首先這個方法是執行緒安全的,其次這個方法使用final修飾,不能重寫,這也就是避免組件的組件的狀態被隨意篡改,
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方法,當我們點進這個方法,發現是一個抽象方法,真正的實作交給子類,這里使用了模板方法的設計模式,當我們按ctrl+alt+B,發現實作他的有一大堆,此時不要驚慌,現在我們分析的事service方法,所以我們找和service相關的實作類,終于我們找到StandardService,
以后找Tomat組件實作類的時候,基本以Standard開頭,除了Connector,
@Override
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) {
try {
connector.init();
} catch (Exception e) {
}
}
}
}
通過上面的代碼,我們知道此方法主要完成了三件事情:
1)初始化執行引擎,
2)初始化執行器(執行緒池),如果配置了,
3)初始化連接器,
詳細的執行程序后面會有講解,這里我們只是了解一個大體的結構,想一想,這里的初始化程序是否可以顛倒?
3.3.4 Engine初始化(1.2.4:init)
代碼的開始還是一如既往的初始化當前組件的狀態,然后通過呼叫initInternal方法實作組件的初始化,所以我們重點看一下org.apache.catalina.core.StandardEngine#initInternal方法,當我們進入此方法,發現只有兩行代碼,
@Override
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.
getRealm();
super.initInternal();
}
這里我們重點看super.initInternal()方法,org.apache.catalina.core.ContainerBase#initInternal,這是容器基本類的初始化方法,
@Override
protected void initInternal() throws LifecycleException {
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() + "-startStop-"));
startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}
通過上面的代碼,我們了解到執行引擎Engine主要初始化一個執行緒池startStopExecutor,不知道大家是否用過阿里的代碼檢查工具,當我們使用 Executors.newSingleThreadExecutor()創建執行緒池的時候就會報錯,這里就是一個典范,大家有時間可以學習一下,
3.3.5 Executor初始化(1.2.5:init)
這個方法的初始化也十分簡單,并沒有做什么事,無非就是設定個狀態什么的,
3.3.6 Connector初始化(1.2.6:init)
按照常理,我們依舊進入org.apache.catalina.connector.Connector#initInternal方法,這個Connector并沒有按照之前那種以Standard開頭,這就是一個實作類,在這個方法中,主要完成如下幾件事:
1)創建CoyoteAdapter,這個類主要用于將http請求封裝成request、response物件
2)將CoyoteAdapter賦值給協議處理器protocolHandler
3)protocolHandler初始化
在這里,想一想protocolHandler是什么時候初始化的?
3.3.7 ProtocolHandler初始化(1.2.7:init)
protocolHandler.init();
->org.apache.coyote.http11.AbstractHttp11Protocol#init
->org.apache.coyote.AbstractProtocol#init
通過如下的跟蹤路徑,我們找到了endPoint,這里重點看一下endPoint初始化org.apache.tomcat.util.net.AbstractEndpoint #init,在這個方法中,我們看到有個bind()的方法,我們點進入,發現有三個實作,AprEndpoint、Nio2EndPoint和NioEndpoint,

這里簡單提一下Nio2Endpoint和NioEndpoint,NioEndpoint屬于同步非阻塞類,而Nio2Endpoint屬于異步非阻塞類,這也就決定了NioEndpoint的請求呼叫鏈為acceptor->poller->執行緒池;而Nio2Endpoint的請求呼叫路徑為acceptor->執行緒池,這里可以想一下為什么Nio2Endpoint少了poller的呼叫,
當然,針對于當前版本的tomcat(8.5.59),默認的還是使用NioEndpoint,這個第一章有簡單介紹,剩下的時間就是自己捋代碼找到這個位置,在org.apache.tomcat.util.net.NioEndpoint#bind方法中,規規矩矩寫了一遍NioSocket編程,不熟悉Socket編程的借此機會可以熟悉一下,
到此位置,Tomcat各組件的初始化作業算是告一段落,接下來,就是真正的Tomcat啟動了,
3.4 組件的啟動(1.3:start)
到目前為止,我們逐漸熟悉了Tomcat的套路了,這里仍然是通過反射的方式呼叫Catalina的start方法,
public void start() throws Exception {
if (catalinaDaemon == null) {
init();
}
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
3.4.1 Catalina啟動(1.3.1:start)
Catalina啟動主要完成了三件事:
1)server組件的啟動;
2)設定關閉程式的鉤子方法;
3)開啟一個執行緒,用于監聽8005埠
第一件事我們稍后再說,現在我們主要說一下第二、第三件事,
3.4.1.1 設定關閉程式的鉤子方法
在這里,使用了jdk的一個方法Runtime.getRuntime().addShutdownHook(),實作了Tomcat的安全關閉,
在這里,我們可以了解一下鉤子方法的觸發時機:
1.程式正常退出
2.使用System.exit()
3.終端使用Ctrl+C觸發的中斷
4.系統關閉
5.OutOfMemory宕機
6.使用Kill pid命令干掉行程(但是在使用kill -9 pid時,是不會被呼叫的)
3.4.1.2 開啟8005埠
我們一路追蹤,org.apache.catalina.startup.Catalina#await->org.apache.catalina.core.StandardServer#await,發現這里開啟一個8005埠的socket服務,這是做什么用的?
不知道大家是否有發現,針對于腳本啟動tomcat和服務類啟動tomcat,他們是怎么關閉tomcat的?
1)針對腳本啟動的tomcat,有一個shutdown.bat或者是shutdown.sh檔案,這個檔案可以幫組我們關閉tomcat服務,
2)針對Windows下的服務類Tomcat,服務界面有一個stop的按鈕,可以關閉tomcat,
而這兩種關閉的方式,其實就是發送一個8005的socket請求,服務端接受到這個請求后,安全的將Tomcat關閉,
3.4.2 Server組件啟動(1.3.2:start)
這里仍然遵循上面的套路,通過呼叫父類的start()方法,經過一系列的狀態設定之后,呼叫真正的實作方法startInternal,
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
for (Service service : services) {
service.start();
}
}
}
這里很簡單,主要遍歷啟動service,
3.4.3 Service組件啟動(1.3.3:start)
在這里,和service啟動一樣,主要做了三件事:
protected void startInternal() throws LifecycleException {
engine.start();
executor.start();
connector.start();
}
3.4.4 Engine組件啟動(1.3.4:start)
org.apache.catalina.core.StandardEngine#startInternal->org.apache.catalina.core.ContainerBase#startInternal
在這里,主要呼叫threadStart()方法,
protected void threadStart() {
if (thread != null)
return;
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
在這里,我們主要關注ContainerBackgroundProcessor這個方法,tomat應用的動態部署就是通過這個類實作了,而這個類的官方說明如下:
/** * Private runnable class to invoke the backgroundProcess method * of this container and its children after a fixed delay. * 固定的可運行類,用于在固定延遲后呼叫此容器及其子級的backgroundProcess方法, */
以后也許會針對tomat的動態部署做一個專題,這里限于篇幅就不說了,
3.4.5 Executor組件啟動(1.3.5:start)
我們快速來到org.apache.catalina.core.StandardThreadExecutor#startInternal,
@Override
protected void startInternal() throws LifecycleException {
taskqueue = new TaskQueue(maxQueueSize);
TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
executor.setThreadRenewalDelay(threadRenewalDelay);
//預啟動最小的核心空閑執行緒
if (prestartminSpareThreads) {
executor.prestartAllCoreThreads();
}
taskqueue.setParent(executor);
setState(LifecycleState.STARTING);
}
在這里,我們看到,還是以之前那種方式創建了執行緒池,這里也沒啥說的,
3.4.6 Connector組件啟動(1.3.6:start)
我們快速來到org.apache.catalina.connector.Connector#startInternal,
@Override
protected void startInternal() throws LifecycleException {
// Validate settings before starting
if (getPort() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPort())));
}
setState(LifecycleState.STARTING);
try {
protocolHandler.start();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
}
}
是不是感覺很神奇?原來大神都是這么寫代碼?看似一段轟轟烈烈的代碼,其實有用的就是那么一兩句,然而體現代碼功底的確實那些看似“沒用的代碼”,這些“沒用的代碼”才讓整個系統具有更強的健壯性,一言不合就拋例外,言歸正傳,這里其實就是實作了protocolHandler.start();啟動協議處理器,
3.4.7 ProtocolHandler組件啟動(1.3.7:start)
我們依舊快速定位到org.apache.coyote.AbstractProtocol#start方法,這里有一句重要的代碼endpoint.start();,我們繼續往下跟蹤:
org.apache.tomcat.util.net.AbstractEndpoint#start->org.apache.tomcat.util.net.NioEndpoint#startInternal
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
// Create worker collection
if ( getExecutor() == null ) {
createExecutor();
}
//10000個
initializeConnectionLatch();
// Start poller threads
//啟動poller執行緒,根據
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
startAcceptorThreads();
}
}
還記得我在上面說過NIOEndPoint和NIO2Endpoint的區別嗎?我們仔細看一下這一段代碼,先后啟動了兩型別執行緒,pollerThread和AcceptorThread,系統默認Acceptor有一個執行緒,pollerThread有兩個執行緒,
現在,我們啟動tomat程式,并啟動jconsole觀察一下執行緒,

到此為止,Tomcat的啟動流程算是告一段落了,然而,Tomcat的原始碼并沒有結束,以后會以專題形式針對特殊處理邏輯做一分享,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/184848.html
標籤:其他
上一篇:天府之國成都!2年Java后端小伙三面招銀網路輕松拿下offer
下一篇:攤牌了,我要手寫一個RPC
