Tomcat源碼分析 (七)----- Tomcat 啟動過程(二)

  • 2019 年 10 月 3 日
  • 筆記

在上一篇文章中,我們分析了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組件的啟動,一層層往下傳遞,直到最後全部啟動完成。

Tags: