Tomcat源碼分析 | 啟動(dòng)過(guò)程深度解析
前言
前文已述,Tomcat 的初始化由 Bootstrap 反射調(diào)用 Catalina 的 load 方法完成,包括解析 server.xml、實(shí)例化各組件、初始化組件等步驟。此番,我們將深入探究 Tomcat 如何啟動(dòng) Web 應(yīng)用,并解析其加載 ServletContextListener 及 Servlet 的機(jī)制。
Tomcat 啟動(dòng)邏輯層層遞進(jìn),各部件協(xié)同運(yùn)作。其啟動(dòng)流程自上而下,依次啟動(dòng)各個(gè)組件,如圖:
圖片
承接前文,我們已解析了 Catalina.load() 方法,接下來(lái)將深入探討 daemon.start() 方法的執(zhí)行過(guò)程。
Bootstrap
daemon.start()
啟動(dòng)過(guò)程與初始化類似,均由 Bootstrap 反射調(diào)用 Catalina 的 start 方法。
public void start()
throws Exception {
if( catalinaDaemnotallow==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
Catalina
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
//調(diào)用Server的start方法,啟動(dòng)Server組件
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// Register shutdown hook
// 注冊(cè)勾子,用于安全關(guān)閉tomcat
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
// Bootstrap中會(huì)設(shè)置await為true,其目的在于讓tomcat在shutdown端口阻塞監(jiān)聽(tīng)關(guān)閉命令
if (await) {
await();
stop();
}
}
Server
在先前的 Lifecycle 文章中,我們已闡述 StandardServer 重寫(xiě)了 startInternal 方法,并在此基礎(chǔ)上實(shí)現(xiàn)了自身的啟動(dòng)邏輯。
StandardServer.startInternal
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
在啟動(dòng)過(guò)程中,LifecycleBase 首先發(fā)出 STARTING_PREP 事件,StandardServer 額外還會(huì)發(fā)出 CONFIGURE_START_EVENT 和 STARTING 事件,通知 LifecycleListener 在啟動(dòng)前進(jìn)行準(zhǔn)備工作。例如,NamingContextListener 會(huì)處理 CONFIGURE_START_EVENT 事件,實(shí)例化 Tomcat 相關(guān)的上下文以及 ContextResource 資源。
隨后,啟動(dòng) Service 組件,這部分邏輯將在后續(xù)文章中詳細(xì)分析。最后,LifecycleBase 發(fā)出 STARTED 事件,完成啟動(dòng)流程。
Service
StandardService 的 start 代碼如下所示:
- 啟動(dòng) Engine,Engine 的子容器也會(huì)被啟動(dòng),Web 應(yīng)用的部署將在這一步驟完成;
- 啟動(dòng) Executor,這是 Tomcat 使用 Lifecycle 封裝的線程池,繼承自 java.util.concurrent.Executor 以及 Tomcat 的 Lifecycle 接口;
- 啟動(dòng) Connector 組件,Connector 完成 Endpoint 的啟動(dòng),此時(shí)意味著 Tomcat 可以對(duì)外提供請(qǐng)求服務(wù)。
StandardService.startInternal
protected void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
// 啟動(dòng)Engine
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
// 啟動(dòng)Executor線程池
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
// 啟動(dòng)MapperListener
mapperListener.start();
// 啟動(dòng)Connector
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
} catch (Exception e) {
// logger......
}
}
}
}
Engine
StandardEngine 的標(biāo)準(zhǔn)實(shí)現(xiàn)為 org.apache.catalina.core.StandardEngine。其構(gòu)造函數(shù)的主要職責(zé)是使用默認(rèn)的基礎(chǔ)閥門(mén)創(chuàng)建 StandardEngine 組件。
/**
* Create a new StandardEngine component with the default basic Valve.
*/
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());
/* Set the jmvRoute using the system property jvmRoute */
try {
setJvmRoute(System.getProperty("jvmRoute"));
} catch(Exception ex) {
log.warn(sm.getString("standardEngine.jvmRouteFail"));
}
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;
}
讓我們來(lái)深入分析 StandardEngine.startInternal 方法。
StandardEngine.startInternal
@Override
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if(log.isInfoEnabled())
log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
// Standard container startup
super.startInternal();
}
StandardEngine、StandardHost、StandardContext、StandardWrapper 這幾個(gè)容器之間存在著父子關(guān)系。一個(gè)父容器可以包含多個(gè)子容器,并且每個(gè)子容器都對(duì)應(yīng)一個(gè)父容器。Engine 是頂層父容器,它沒(méi)有父容器。各個(gè)組件的包含關(guān)系如下圖所示:
圖片
默認(rèn)情況下,StandardEngine 只有一個(gè)子容器 StandardHost,一個(gè) StandardContext 對(duì)應(yīng)一個(gè) Web 應(yīng)用,而一個(gè) StandardWrapper 對(duì)應(yīng)一個(gè) Web 應(yīng)用里面的一個(gè) Servlet。
StandardEngine、StandardHost、StandardContext、StandardWrapper 都是繼承自 ContainerBase,各個(gè)容器的啟動(dòng)是由父容器調(diào)用子容器的 start 方法完成的,也就是說(shuō)由 StandardEngine 啟動(dòng) StandardHost,再由 StandardHost 啟動(dòng) StandardContext,以此類推。
由于它們都繼承自 ContainerBase,當(dāng)調(diào)用 start 啟動(dòng) Container 容器時(shí),首先會(huì)執(zhí)行 ContainerBase 的 start 方法。ContainerBase 會(huì)尋找子容器,并在線程池中啟動(dòng)子容器。StandardEngine 也不例外。
ContainerBase
ContainerBase 的 startInternal 方法如下所示,主要分為以下三個(gè)步驟:
- 啟動(dòng)子容器
- 啟動(dòng) Pipeline,并發(fā)出 STARTING 事件
- 如果 backgroundProcessorDelay 參數(shù) >= 0,則開(kāi)啟 ContainerBackgroundProcessor 線程,用于調(diào)用子容器的 backgroundProcess 方法
protected synchronized void startInternal() throws LifecycleException {
// 省略若干代碼......
// 把子容器的啟動(dòng)步驟放在線程中處理,默認(rèn)情況下線程池只有一個(gè)線程處理任務(wù)隊(duì)列
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])));
}
// 阻塞當(dāng)前線程,直到子容器start完成
boolean fail = false;
for (Future<Void> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
fail = true;
}
}
// 啟用Pipeline
if (pipeline instanceof Lifecycle)
((Lifecycle) pipeline).start();
setState(LifecycleState.STARTING);
// 開(kāi)啟ContainerBackgroundProcessor線程用于調(diào)用子容器的backgroundProcess方法,默認(rèn)情況下backgroundProcessorDelay=-1,不會(huì)啟用該線程
threadStart();
}
ContainerBase 會(huì)將 StartChild 任務(wù)丟給線程池處理,并獲取 Future 對(duì)象。然后,它會(huì)遍歷所有 Future,并進(jìn)行阻塞式的 result.get() 操作,這將異步啟動(dòng)轉(zhuǎn)換為同步啟動(dòng),只有所有子容器都啟動(dòng)完成才會(huì)繼續(xù)運(yùn)行。
我們?cè)賮?lái)看看提交到線程池的 StartChild 任務(wù),它實(shí)現(xiàn)了 java.util.concurrent.Callable 接口,并在 call 方法中完成子容器的 start 動(dòng)作。
private static class StartChild implements Callable<Void> {
private Container child;
public StartChild(Container child) {
this.child = child;
}
@Override
public Void call() throws LifecycleException {
child.start();
return null;
}
}
啟動(dòng) Pipeline
默認(rèn)使用 StandardPipeline 實(shí)現(xiàn)類,它也是一個(gè) Lifecycle。在容器啟動(dòng)時(shí),StandardPipeline 會(huì)遍歷 Valve 鏈表,如果 Valve 是 Lifecycle 的子類,則會(huì)調(diào)用其 start 方法啟動(dòng) Valve 組件,代碼如下:
public class StandardPipeline extends LifecycleBase
implements Pipeline, Contained {
protected synchronized void startInternal() throws LifecycleException {
Valve current = first;
if (current == null) {
current = basic;
}
while (current != null) {
if (current instanceof Lifecycle)
((Lifecycle) current).start();
current = current.getNext();
}
setState(LifecycleState.STARTING);
}
}
Host
在分析 Host 時(shí),我們可以從 Host 的構(gòu)造函數(shù)入手,該方法主要負(fù)責(zé)設(shè)置基礎(chǔ)閥門(mén)。
ublic StandardHost() {
super();
pipeline.setBasic(new StandardHostValve());
}
StandardEngine.startInternal
protected synchronized void startInternal() throws LifecycleException {
// errorValve默認(rèn)使用org.apache.catalina.valves.ErrorReportValve
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
// 如果所有的閥門(mén)中已經(jīng)存在這個(gè)實(shí)例,則不進(jìn)行處理,否則添加到 Pipeline 中
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
// 如果未找到則添加到 Pipeline 中,注意是添加到 basic valve 的前面
// 默認(rèn)情況下,first valve 是 AccessLogValve,basic 是 StandardHostValve
if(!found) {
Valve valve =
(Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
// 處理異常,省略......
}
}
// 調(diào)用父類 ContainerBase,完成統(tǒng)一的啟動(dòng)動(dòng)作
super.startInternal();
}
StandardHost Pipeline 包含的 Valve 組件:
- basic:org.apache.catalina.core.StandardHostValve
- first:org.apache.catalina.valves.AccessLogValve
需要注意的是,在往 Pipeline 中添加 Valve 閥門(mén)時(shí),是添加到 first 后面,basic 前面。
Context
接下來(lái),讓我們深入分析 Context 的實(shí)現(xiàn)類 org.apache.catalina.core.StandardContext。
首先,我們來(lái)看看構(gòu)造方法,該方法用于設(shè)置 Context.pipeline 的基礎(chǔ)閥門(mén)。
public StandardContext() {
super();
pipeline.setBasic(new StandardContextValve());
broadcaster = new NotificationBroadcasterSupport();
// Set defaults
if (!Globals.STRICT_SERVLET_COMPLIANCE) {
// Strict servlet compliance requires all extension mapped servlets
// to be checked against welcome files
resourceOnlyServlets.add("jsp");
}
}
啟動(dòng)方法與之前分析的容器啟動(dòng)方法類似,這里不再贅述。
Wrapper
Wrapper 是一個(gè) Servlet 的包裝,我們先來(lái)看看構(gòu)造方法。其主要作用是設(shè)置基礎(chǔ)閥門(mén) StandardWrapperValve。
public StandardWrapper() {
super();
swValve=new StandardWrapperValve();
pipeline.setBasic(swValve);
broadcaster = new NotificationBroadcasterSupport();
}
這里每個(gè)容器中的 pipeline 設(shè)置的 StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve 都是非常重要的,它們?cè)?HTTP 請(qǐng)求處理過(guò)程中扮演著關(guān)鍵角色,我們將在后續(xù)的文章中詳細(xì)講解。
結(jié)語(yǔ)
至此,整個(gè)啟動(dòng)過(guò)程便告一段落。整個(gè)啟動(dòng)過(guò)程由父組件控制子組件的啟動(dòng),一層層往下傳遞,直到最后全部啟動(dòng)完成。