在上一篇文章中,我們分析了tomcat的初始化過程,是由Bootstrap反射調用Catalina的load方法完成tomcat的初始化,包括server.xml的解析、實例化各大組件、初始化組件等邏輯。那么tomcat又是如何啟動webapp應用,又是如何加載應用程序的ServletContextListener,以及Servlet呢?我們將在這篇文章進行分析
我們先來看下整體的啟動邏輯,tomcat由上往下,挨個啟動各個組件:
我們接着上一篇文章來分析,上一篇文章我們分析完了Catalina.load(),這篇文章來看看daemon.start();
Bootstrap
daemon.start()
啟動過程和初始化一樣,由Bootstrap反射調用Catalina的start方法
public void start() throws Exception { if( catalinaDaemon==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 { //調用Server的start方法,啟動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 // 注冊勾子,用於安全關閉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中會設置await為true,其目的在於讓tomcat在shutdown端口阻塞監聽關閉命令 if (await) { await(); stop(); } }
Server
在前面的Lifecycle文章中,我們介紹了StandardServer重寫了startInternal方法,完成自己的邏輯
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(); } } }
先是由LifecycleBase統一發出STARTING_PREP事件,StandardServer額外還會發出CONFIGURE_START_EVENT、STARTING事件,用於通知LifecycleListener在啟動前做一些准備工作,比如NamingContextListener會處理CONFIGURE_START_EVENT事件,實例化tomcat相關的上下文,以及ContextResource資源
接着,啟動Service組件,這一塊的邏輯將在下面進行詳細分析,最后由LifecycleBase發出STARTED事件,完成start
Service
StandardService的start代碼如下所示:
1. 啟動Engine,Engine的child容器都會被啟動,webapp的部署會在這個步驟完成;
2. 啟動Executor,這是tomcat用Lifecycle封裝的線程池,繼承至java.util.concurrent.Executor以及tomcat的Lifecycle接口
3. 啟動Connector組件,由Connector完成Endpoint的啟動,這個時候意味着tomcat可以對外提供請求服務了
StandardService.startInternal
protected void startInternal() throws LifecycleException { setState(LifecycleState.STARTING); // 啟動Engine if (engine != null) { synchronized (engine) { engine.start(); } } // 啟動Executor線程池 synchronized (executors) { for (Executor executor: executors) { executor.start(); } } // 啟動MapperListener mapperListener.start(); // 啟動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
Engine的標准實現為org.apache.catalina.core.StandardEngine
。我們先來看看構造函數。其主要職責為:使用默認的基礎閥門創建標准Engine組件。
/** * 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; }
我們來看看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各個容器存在父子關系,一個父容器包含多個子容器,並且一個子容器對應一個父容器。Engine是頂層父容器,它不存在父容器。各個組件的包含關系如下圖所示,默認情況下,StandardEngine只有一個子容器StandardHost,一個StandardContext對應一個webapp應用,而一個StandardWrapper對應一個webapp里面的一個 Servlet
StandardEngine、StandardHost、StandardContext、StandardWrapper都是繼承至ContainerBase,各個容器的啟動,都是由父容器調用子容器的start方法,也就是說由StandardEngine啟動StandardHost,再StandardHost啟動StandardContext,以此類推。
由於它們都是繼續至ContainerBase,當調用 start 啟動Container容器時,首先會執行 ContainerBase 的 start 方法,它會尋找子容器,並且在線程池中啟動子容器,StandardEngine也不例外。
ContainerBase
ContainerBase的startInternal方法如下所示,主要分為以下3個步驟:
1. 啟動子容器
2. 啟動Pipeline,並且發出STARTING事件
3. 如果backgroundProcessorDelay參數 >= 0,則開啟ContainerBackgroundProcessor線程,用於調用子容器的backgroundProcess
protected synchronized void startInternal() throws LifecycleException { // 省略若干代碼...... // 把子容器的啟動步驟放在線程中處理,默認情況下線程池只有一個線程處理任務隊列 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]))); } // 阻塞當前線程,直到子容器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); // 開啟ContainerBackgroundProcessor線程用於調用子容器的backgroundProcess方法,默認情況下backgroundProcessorDelay=-1,不會啟用該線程 threadStart(); }
ContainerBase會把StartChild任務丟給線程池處理,得到Future,並且會遍歷所有的Future進行阻塞result.get(),這個操作是將異步啟動轉同步,子容器啟動完成才會繼續運行。我們再來看看submit到線程池的StartChild任務,它實現了java.util.concurrent.Callable接口,在call里面完成子容器的start動作
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; } }
啟動Pipeline
默認使用 StandardPipeline 實現類,它也是一個Lifecycle。在容器啟動的時候,StandardPipeline 會遍歷 Valve 鏈表,如果 Valve 是 Lifecycle 的子類,則會調用其 start 方法啟動 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的時候,我們從Host的構造函數入手,該方法主要是設置基礎閥門。
public StandardHost() { super(); pipeline.setBasic(new StandardHostValve()); }
StandardEngine.startInternal
protected synchronized void startInternal() throws LifecycleException { // errorValve默認使用org.apache.catalina.valves.ErrorReportValve String errorValve = getErrorReportValveClass(); if ((errorValve != null) && (!errorValve.equals(""))) { try { boolean found = false; // 如果所有的閥門中已經存在這個實例,則不進行處理,否則添加到 Pipeline 中 Valve[] valves = getPipeline().getValves(); for (Valve valve : valves) { if (errorValve.equals(valve.getClass().getName())) { found = true; break; } } // 如果未找到則添加到 Pipeline 中,注意是添加到 basic valve 的前面 // 默認情況下,first valve 是 AccessLogValve,basic 是 StandardHostValve if(!found) { Valve valve = (Valve) Class.forName(errorValve).getConstructor().newInstance(); getPipeline().addValve(valve); } } catch (Throwable t) { // 處理異常,省略...... } } // 調用父類 ContainerBase,完成統一的啟動動作 super.startInternal(); }
StandardHost Pipeline 包含的 Valve 組件:
1. basic:org.apache.catalina.core.StandardHostValve
2. first:org.apache.catalina.valves.AccessLogValve
需要注意的是,在往 Pipeline 中添加 Valve 閥門時,是添加到 first 后面,basic 前面
Context
接下來我們分析一下Context的實現org.apache.catalina.core.StandardContext
。
先來看看構造方法,該方法用於設置Context.pipeline
的基礎閥門。
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"); } }
啟動方法和上面的容器啟動方法類似,我們就不再贅述了
Wrapper
Wrapper是一個Servlet的包裝,我們先來看看構造方法。主要作用就是設置基礎閥門StandardWrapperValve
。
public StandardWrapper() { super(); swValve=new StandardWrapperValve(); pipeline.setBasic(swValve); broadcaster = new NotificationBroadcasterSupport(); }
這里每個容器中的pipeline設置的StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve是有大用處的,后面我們會在Http請求過程中詳細講解。
總結
至此,整個啟動過程便告一段落。整個啟動過程程,由parent組件控制child組件的啟動,一層層往下傳遞,直到最后全部啟動完成。