天天看點

tomcat啟動過程分析tomcat啟動過程

tomcat啟動過程

       tomcat中最頂層的容器叫server,代表伺服器,server中起碼有一個service,提供服務。service中包含兩部分:connector和Container。connector處理連接配接相關的功能,包括socket與request、response的轉化。Container負責封裝管理servlet以及處理request請求。一個tomcat隻有一個server,一個server包含多個service,一個service隻有一個Container,但是可以有多個connector(使用相同協定或者不同協定提供不同端口的連接配接),tomcat結構如圖所示:

tomcat啟動過程分析tomcat啟動過程

       tomcat的Server由org.apache.catalina.startup.Catalina來管理,Catalina是整個tomcat的管理類,有load、start、stop三個方法來管理整個伺服器的生命周期,load方法用于根據conf/server.xml檔案建立server并根據init方法進行初始化;start方法啟動伺服器,stop方法停止伺服器;start和stop方法分别調用server的start和stop方法,load調用server的init方法,然後這三個方法在按容器結構逐層調用對應方法。例如server的start方法會調用service方法,service的start方法又會調用connectors和container的start方法,然後整個tomcat服務就啟動了。init方法和start方法也類似,這就是tomcat的生命周期。Catalina還有個await方法,Catalina的await方法調用了server的await方法,使的主線程不會退出。

       tomcat的main方法不在Catalina中,而是在org.apache.catalina.startup.Bootstrap中,Bootstrap作用類似于CatalinaAdaptor,具體處理過程使用Catalina完成,這樣啟動入口和管理類可以分開,可以友善的創造出多種啟動方式,每種方式隻要設計對應的CatalinaAdaptor即可。

Bootstrap的啟動過程

       bootstrap是tomcat的入口,啟動tomcat即是調用bootstrap的main方法:

public static void main(String[] args)
  {
    if (daemon == null)
    {
      Bootstrap bootstrap = new Bootstrap();
      try
      {
        bootstrap.init();
      }
      catch (Throwable t)
      {
        handleThrowable(t);
        t.printStackTrace();
        return;
      }
      daemon = bootstrap;
    }
    else
    {
      Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
    }
    try
    {
      String command = "start";
      if (args.length > ) {
        command = args[(args.length - )];
      }
      if (command.equals("startd"))
      {
        args[(args.length - )] = "start";
        daemon.load(args);
        daemon.start();
      }
      else if (command.equals("stopd"))
      {
        args[(args.length - )] = "stop";
        daemon.stop();
      }
      else if (command.equals("start"))
      {
        daemon.setAwait(true);
        daemon.load(args);
        daemon.start();
      }
      else if (command.equals("stop"))
      {
        daemon.stopServer(args);
      }
      else if (command.equals("configtest"))
      {
        daemon.load(args);
        if (null == daemon.getServer()) {
          System.exit();
        }
        System.exit();
      }
      else
      {
        log.warn("Bootstrap: command \"" + command + "\" does not exist.");
      }
    }
    catch (Throwable t)
    {
      if (((t instanceof InvocationTargetException)) && (t.getCause() != null)) {
        t = t.getCause();
      }
      handleThrowable(t);
      t.printStackTrace();
      System.exit();
    }
  }
           

       main方法有兩部分内容:首先建立Bootstrap,并執行init方法;然後處理main的參數,如果參數為空,預設執行start方法。

       在init中初始化了ClassLoader,并使用Classloader建立了Catalina執行個體,指派給catalinaDaemon,後面操作都使用catalinaDaemon來執行。

       對于start指令處理調用了三個方法:setAwait(true)、load(args)和start(),這三個方法内部都調用了Catalina的相應方法,不過是通過反射來調用的。start方法(另外兩個也類似):

public void start() throws Exception {
    if (this.catalinaDaemon == null) {
      init();
    }
    Method method = this.catalinaDaemon.getClass().getMethod("start", (Class[])null);
    method.invoke(this.catalinaDaemon, (Object[])null);
  }
           

首先判斷catalinaDaemon有沒有初始化,沒有的話進行init,然後通過Method進行反射調用Catalina的start方法。

Catalina的啟動過程

       Catalina主要調用setAwait、load和start來完成啟動。setAwait用于設定Server啟動完成後時候進入等待狀态;load用于加載配置檔案,并初始化server;start用于啟動伺服器。

       setAwait方法如下:

public void setAwait(boolean b)
  {
    this.await = b;
  }
           

setAwait方法就是設定await的值,start方法啟動伺服器後會使用它的值來判斷是否進入等待狀态。

       Catalina的load方法根據conf/server.xml來建立server方法,并進行指派,然後調用server的init方法:

public void load()
  {
    long t1 = System.nanoTime();

    initDirs();

    initNaming();

    Digester digester = createStartDigester();

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;
    try
    {
      try
      {
        file = configFile();
        inputStream = new FileInputStream(file);
        inputSource = new InputSource(file.toURI().toURL().toString());
      }
      catch (Exception e)
      {
        if (log.isDebugEnabled()) {
          log.debug(sm.getString("catalina.configFail", new Object[] { file }), e);
        }
      }
      if (inputStream == null) {
        try
        {
          inputStream = getClass().getClassLoader().getResourceAsStream(getConfigFile());

          inputSource = new InputSource(getClass().getClassLoader().getResource(getConfigFile()).toString());
        }
        catch (Exception e)
        {
          if (log.isDebugEnabled()) {
            log.debug(sm.getString("catalina.configFail", new Object[] { getConfigFile() }), e);
          }
        }
      }
      if (inputStream == null) {
        try
        {
          inputStream = getClass().getClassLoader().getResourceAsStream("server-embed.xml");

          inputSource = new InputSource(getClass().getClassLoader().getResource("server-embed.xml").toString());
        }
        catch (Exception e)
        {
          if (log.isDebugEnabled()) {
            log.debug(sm.getString("catalina.configFail", new Object[] { "server-embed.xml" }), e);
          }
        }
      }
      if ((inputStream == null) || (inputSource == null))
      {
        if (file == null)
        {
          log.warn(sm.getString("catalina.configFail", new Object[] { getConfigFile() + "] or [server-embed.xml]" }));
        }
        else
        {
          log.warn(sm.getString("catalina.configFail", new Object[] { file.getAbsolutePath() }));
          if ((file.exists()) && (!file.canRead())) {
            log.warn("Permissions incorrect, read permission is not allowed on the file.");
          }
        }
        return;
      }
      try
      {
        inputSource.setByteStream(inputStream);
        digester.push(this);
        digester.parse(inputSource);
      }
      catch (SAXParseException spe)
      {
        log.warn("Catalina.start using " + getConfigFile() + ": " + spe.getMessage()); return;
      }
      catch (Exception e)
      {
        log.warn("Catalina.start using " + getConfigFile() + ": ", e); return;
      }
      if (inputStream != null) {
        try
        {
          inputStream.close();
        }
        catch (IOException localIOException3) {}
      }
      getServer().setCatalina(this);
    }
    finally
    {
      if (inputStream != null) {
        try
        {
          inputStream.close();
        }
        catch (IOException localIOException4) {}
      }
    }
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    initStreams();
    try
    {
      getServer().init();
    }
    catch (LifecycleException e)
    {
      if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
        throw new Error(e);
      }
      log.error("Catalina.start", e);
    }
    long t2 = System.nanoTime();
    if (log.isInfoEnabled()) {
      log.info("Initialization processed in " + (t2 - t1) / L + " ms");
    }
  }
           

       Catalina的start方法主要調用server的start方法,并根據await來判斷是否讓程式進入等待狀态:

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();
    try
    {
      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) / L + " ms");
    }
    if (this.useShutdownHook)
    {
      if (this.shutdownHook == null) {
        this.shutdownHook = new CatalinaShutdownHook();
      }
      Runtime.getRuntime().addShutdownHook(this.shutdownHook);

      LogManager logManager = LogManager.getLogManager();
      if ((logManager instanceof ClassLoaderLogManager)) {
        ((ClassLoaderLogManager)logManager).setUseShutdownHook(false);
      }
    }
    if (this.await)
    {
      await();
      stop();
    }
  }
           

先判斷Server是否已經存在,如果不存在調用load來初始化Server,然後調用Server的start方法來啟動伺服器。并且注冊關閉鈎子并根據await判斷程式是否進入等待狀态。進入等待狀态會調用await和stop兩個方法,await調用server的await,server内部的await方法會執行while循環,此時程式就停在了await方法,當await退出時,就會執行stop方法關閉伺服器。

Server啟動過程

       Server接口中提供addService(Service service)和removeService(Service service)來添加和删除Service,Server的start和init方法分别循環調用所有Service的init和start方法來啟動所有的Service。

       Server的預設實作是org.apache.catalina.core.StandardServer,StandardServer繼承于LifecycleMBeanBase,lifecycleMbeanBase繼承LifecycleBase,init和start方法定義在LifecycleBase中,LifecycleBase裡的init和start調用initInternal和startInternal方法,這兩個都是模闆方法,由具體子類實作,是以StandardServer的init和start方法執行時會調用自己的initInternal和startInternal方法。StandardServer的initInternal和startInternal方法循環調用了每個service的start和init方法:

protected void startInternal() throws LifecycleException {
    fireLifecycleEvent("configure_start", null);
    setState(LifecycleState.STARTING);

    this.globalNamingResources.start();
    synchronized (this.servicesLock)
    {
      for (int i = ; i < this.services.length; i++) {
        this.services[i].start();
      }
    }
  }

  protected void initInternal() throws LifecycleException {
    super.initInternal();

    this.onameStringCache = register(new StringCache(), "type=StringCache");

    MBeanFactory factory = new MBeanFactory();
    factory.setContainer(this);
    this.onameMBeanFactory = register(factory, "type=MBeanFactory");

    this.globalNamingResources.init();
    if (getCatalina() != null)
    {
      ClassLoader cl = getCatalina().getParentClassLoader();
      while ((cl != null) && (cl != ClassLoader.getSystemClassLoader()))
      {
        if ((cl instanceof URLClassLoader))
        {
          URL[] urls = ((URLClassLoader)cl).getURLs();
          for (URL url : urls) {
            if (url.getProtocol().equals("file")) {
              try
              {
                File f = new File(url.toURI());
                if ((f.isFile()) && (f.getName().endsWith(".jar"))) {
                  ExtensionValidator.addSystemResource(f);
                }
              }
              catch (URISyntaxException localURISyntaxException) {}catch (IOException localIOException) {}
            }
          }
        }
        cl = cl.getParent();
      }
    }
    for (int i = ; i < this.services.length; i++) {
      this.services[i].init();
    }
  }
           

StandardServer還實作了await方法,Catalina就是調用它使伺服器進入等待狀态:

public void await()
  {
    if (this.port == -) {
      return;
    }
    if (this.port == -)
    {
      try
      {
        this.awaitThread = Thread.currentThread();
        while (!this.stopAwait) {
          try
          {
            Thread.sleep(L);
          }
          catch (InterruptedException localInterruptedException) {}
        }
      }
      finally
      {
        this.awaitThread = null;
      }
      return;
    }
    try
    {
      this.awaitSocket = new ServerSocket(this.port, , InetAddress.getByName(this.address));
    }
    catch (IOException e)
    {
      log.error("StandardServer.await: create[" + this.address + ":" + this.port + "]: ", e);

      return;
    }
    try
    {
      this.awaitThread = Thread.currentThread();
      while (!this.stopAwait)
      {
        ServerSocket serverSocket = this.awaitSocket;
        if (serverSocket == null) {
          break;
        }
        Socket socket = null;
        StringBuilder command = new StringBuilder();
        try
        {
          long acceptStartTime = System.currentTimeMillis();
          try
          {
            socket = serverSocket.accept();
            socket.setSoTimeout();
            stream = socket.getInputStream();
          }
          catch (SocketTimeoutException ste)
          {
            InputStream stream;
            log.warn(sm.getString("standardServer.accept.timeout", new Object[] { Long.valueOf(System.currentTimeMillis() - acceptStartTime) }), ste);
            try
            {
              if (socket != null) {
                socket.close();
              }
            }
            catch (IOException localIOException1) {}
            continue;
          }
          catch (AccessControlException ace)
          {
            log.warn("StandardServer.accept security exception: " + ace.getMessage(), ace);
            try
            {
              if (socket != null) {
                socket.close();
              }
            }
            catch (IOException localIOException2) {}
            continue;
          }
          catch (IOException e)
          {
            if (this.stopAwait) {
              try
              {
                if (socket != null) {
                  socket.close();
                }
              }
              catch (IOException localIOException3) {}
            }
            log.error("StandardServer.await: accept: ", e);
          }
          InputStream stream;
          int expected = ;
          while (expected < this.shutdown.length())
          {
            if (this.random == null) {
              this.random = new Random();
            }
            expected += this.random.nextInt() % ;
          }
          while (expected > )
          {
            int ch = -;
            try
            {
              ch = stream.read();
            }
            catch (IOException e)
            {
              log.warn("StandardServer.await: read: ", e);
              ch = -;
            }
            if ((ch < ) || (ch == )) {
              break;
            }
            command.append((char)ch);
            expected--;
          }
          try
          {
            if (socket != null) {
              socket.close();
            }
          }
          catch (IOException localIOException5) {}
          match = command.toString().equals(this.shutdown);
        }
        finally
        {
          try
          {
            if (socket != null) {
              socket.close();
            }
          }
          catch (IOException localIOException6) {}
        }
        boolean match;
        if (match)
        {
          log.info(sm.getString("standardServer.shutdownViaPort"));
          break;
        }
        log.warn("StandardServer.await: Invalid command '" + command.toString() + "' received");
      }
    }
    finally
    {
      ServerSocket serverSocket;
      ServerSocket serverSocket = this.awaitSocket;
      this.awaitThread = null;
      this.awaitSocket = null;
      if (serverSocket != null) {
        try
        {
          serverSocket.close();
        }
        catch (IOException localIOException8) {}
      }
    }
  }
           

await的大緻邏輯為先判斷端口号port:

- port為-2,直接退出

- port為-1,會進入while(!stopAwait)的循環,并且内部沒有break語句,stopAwait隻有調用了stop才會變成true,是以此情況下隻有外部調用stop才會退出循環

- port為其他值,會進入while(!stopAwait)循環,同時在port所在端口啟動serverSocket監聽關閉指令,如果接受到了則使用break調出循環。

這裡的port為在配置檔案中設定的shutdown端口,如果不通過網絡指令來關閉tomcat,将port設定為-1即可。await對于接受的參數進行了一定處理,ASCII碼小于32及其後面的部分直接丢棄。

Service的啟動

       Service的預設實作為org.apache.catalina.core.StandardService,StandardService也繼承LifecycleMbeanBase,是以init和start也會調用initInternal和startInternal:

protected void initInternal()
    throws LifecycleException
  {
    super.initInternal();
    if (this.container != null) {
      this.container.init();
    }
    for (Executor executor : findExecutors())
    {
      if ((executor instanceof JmxEnabled)) {
        ((JmxEnabled)executor).setDomain(getDomain());
      }
      executor.init();
    }
    this.mapperListener.init();
    synchronized (this.connectorsLock)
    {
      for (Connector connector : this.connectors) {
        try
        {
          connector.init();
        }
        catch (Exception e)
        {
          String message = sm.getString("standardService.connector.initFailed", new Object[] { connector });

          log.error(message, e);
          if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
            throw new LifecycleException(message);
          }
        }
      }
    }
  }

protected void startInternal()
    throws LifecycleException
  {
    if (log.isInfoEnabled()) {
      log.info(sm.getString("standardService.start.name", new Object[] { this.name }));
    }
    setState(LifecycleState.STARTING);
    if (this.container != null) {
      synchronized (this.container)
      {
        this.container.start();
      }
    }
    synchronized (this.executors)
    {
      for (Executor executor : this.executors) {
        executor.start();
      }
    }
    this.mapperListener.start();
    synchronized (this.connectorsLock)
    {
      for (Connector connector : this.connectors) {
        try
        {
          if (connector.getState() != LifecycleState.FAILED) {
            connector.start();
          }
        }
        catch (Exception e)
        {
          log.error(sm.getString("standardService.connector.startFailed", new Object[] { connector }), e);
        }
      }
    }
  }
           

StandardService中的initInternal和startInternal方法主要調用container、executors、mapperListener、connectors的init和start方法。MapperListener是Mapper的監聽器,用來監控container的變化,executor是用來在connector管理線程的線程池,在server.xml中有參考的用法,隻不過是被注釋的:

<!--
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
    -->
           

這樣整個tomcat就完成了啟動,tomcat的整個啟動流程如下圖:

tomcat啟動過程分析tomcat啟動過程