Tomcat原始碼分析--啟動流程

surpassLiang發表於2020-10-19

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 &quot;%r&quot; %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的原始碼並沒有結束。以後會以專題形式針對特殊處理邏輯做一分享。

相關文章