Tomcatの最上位構造および起動プロセス


1.Tomcatの最上位構造
Tomcatの最上位レベルのコンテナは、サーバ全体を表すサーバです.サーバには、特定のサービスを提供するための少なくとも1つのサービスが含まれています.
サービスには、次の2つのセクションがあります.
Connector:接続に関する処理、およびRequestおよびresponseへのSocket変換
Container:サーブレットのカプセル化と管理、requestリクエストの処理
1つのTomcatには1つのサーバしかありません.1つのサーバには複数のサービスが含まれ、1つのサービスには1つのContainerしかありませんが、複数のConnectorがあります.1つのコンテナには複数のサービス(1つのTomcatが複数のアプリケーションを配置するなど)が提供され、1つのアプリケーションに複数のリクエストがあるため、1つのサービスに複数のConnectorがあります.
Tomcatのサーバはorg.apache.catalina.startup.Catalina(catalina.jarパッケージ)で管理します.CatalinaはTomcatの管理クラスで、3つの方法があります.
(1)load:conf/serverによる.xmlファイルはサーバを作成し、サーバ内のinitメソッドを呼び出して初期化します.
(2)start:サービスを開始し、サーバのstartメソッドを調整する
(3)stop:サービスを停止し、サーバのstopメソッドを調整する
以上の3つのメソッドは、コンテナの構造に基づいて対応するメソッドをレイヤごとに呼び出します.
Catalinaにはもう一つの重要な方法があります.awaitメソッド、Catalinaのawaitはサーバのawaitメソッドを直接呼び出します.このメソッドの役割は、プライマリ・スレッドが終了しないように、デッドサイクルに入ることです.
ただしTomcatのエントリmainメソッドはCatalinaクラスではなくorg.apache.catalina.startup.Bootstrapにあります.Bootstrapの役割はCatalinaAdaptorと似ていますが、具体的な処理はCatalinaを使用して行われます.このようなメリットは、起動の入口と具体的な管理クラスを分けることができ、多くの起動方式を簡単に作成することができます.各起動方式は対応するCatalinaAdaptorを書くだけでいいです.
注意:アダプタモードでは、1つのクラスのインタフェースをクライアントが期待する別のインタフェースに変換し、インタフェースが一致しないため一緒に動作できない2つのクラスを一緒に動作させることができます.
2、Bootstrapの起動過程
BootstrapはTomcatのエントリであり、通常はTomcatを起動することがBootstrapのmainメソッドです.
public static void main(String[] args)
  {
//  Bootstrap
    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 > 0) {
        command = args[(args.length - 1)];
      }

      if (command.equals("startd")) {
        args[(args.length - 1)] = "start";
        daemon.load(args);
        daemon.start();
      } else if (command.equals("stopd")) {
        args[(args.length - 1)] = "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(1);
        }
        System.exit(0);
      } else {
        log.warn(new StringBuilder().append("Bootstrap: command \"").append(command).append("\" does not exist.").toString());
      }
    }
    catch (Throwable t) {
      if (((t instanceof InvocationTargetException)) && (t.getCause() != null))
      {
        t = t.getCause();
      }
      handleThrowable(t);
      t.printStackTrace();
      System.exit(1);
    }
  }

ソースコードによると、mainの方法は非常に簡単で、2つの部分があります.
(1)Bootstrapを作成し、initメソッドの初期化を行い、
(2)mainメソッドから入力されたコマンドを処理し、argsパラメータが空の場合、デフォルトでstartを実行する
InitメソッドでClassLoaderを初期化し、ClassLoaderでCatalinaインスタンスを作成し、catalina Daemon変数を割り当てます.コマンドの操作はcatalinaDaemonを使用して具体的に実行します.
Initメソッドソース:
public void init()
    throws Exception
  {
    setCatalinaHome();
    setCatalinaBase();

    initClassLoaders();

    Thread.currentThread().setContextClassLoader(this.catalinaLoader);

    SecurityClassLoad.securityClassLoad(this.catalinaLoader);

    if (log.isDebugEnabled())
      log.debug("Loading startup class");
    Class startupClass = this.catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");

    Object startupInstance = startupClass.newInstance();

    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] = this.sharedLoader;
    Method method = startupInstance.getClass().getMethod(methodName, paramTypes);

    method.invoke(startupInstance, paramValues);

    this.catalinaDaemon = startupInstance;
  }

 private void initClassLoaders()
  {
    try
    {
      this.commonLoader = createClassLoader("common", null);
      if (this.commonLoader == null)
      {
        this.commonLoader = getClass().getClassLoader();
      }
      this.catalinaLoader = createClassLoader("server", this.commonLoader);
      this.sharedLoader = createClassLoader("shared", this.commonLoader);
    } catch (Throwable t) {
      handleThrowable(t);
      log.error("Class loader creation threw exception", t);
      System.exit(1);
    }
  }

startコマンド処理ではdaemonの3つのメソッドが呼び出された.setAwait(true);daemon.load(args);daemon.start();この3つのメソッドの内部では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);
  }

注意:Methodはjavaです.lang.reflectパッケージのクラスは、特定のメソッドを表し、その中のinvokeメソッドを使用してマルチ代表メソッドを実行することができます.invokeメソッドには2つのパラメータがあります.1つ目のパラメータはMethodメソッドが存在するエンティティで、2つ目のパラメータはMethodメソッドの実行に必要なパラメータです.
3、Catalinaの起動過程
Catalinaの起動は主にsetawait,load,startメソッドを呼び出す
(1)setawaitメソッドでは,サーバ起動完了後に待機状態に入るか否かのフラグを設定する.
setawaitメソッドソース:
public void setAwait(boolean b) {
    this.await = b;
  }

(2)loadは、プロファイルをロードし、サーバを作成して初期化するために使用されます.
loadメソッドソース:
public void load()
  {
    long t1 = System.nanoTime();

    initDirs();

    initNaming();

    Digester digester = createStartDigester();

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;
    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;
    }
    finally {
      try {
        inputStream.close();
      }
      catch (IOException e)
      {
      }
    }
    getServer().setCatalina(this);

    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) / 1000000L + " ms");
  }

conf/serverによる.xmlはサーバオブジェクトを作成し、serverプロパティに値を付けてserverのinitメソッドを呼び出します.
(3)startサービス開始用
startメソッドソース:
 public void start()
  {
//  Server    ,     load      
    if (getServer() == null) {
      load();
    }
    if (getServer() == null) {
      log.fatal("Cannot start server. Server instance is not configured.");
      return;
    }

    long t1 = System.nanoTime();
    try
    {
  //  Server start      
      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) / 1000000L + " 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);
      }

    }
//  await true,  await         await  , await    while    ,   stop  
    if (this.await) {
      await();
      stop();
    }
  }

4、サーバーの起動
サーバインタフェースは、addServiceメソッドとremoveServiceメソッドを使用してサービスを追加および削除します.サーバのinitメソッドとstartメソッドは、各サービスのinitメソッドとstartメソッドをそれぞれループして呼び出し、すべてのサービスを起動します.
Serverのデフォルト実装はorgです.apache.catalina.core.StandardServer、StandardServerはLifecycleMBeanBaseから継承され、LifecycleMBeanBaseはLifecycleBaseから継承され、initとstartメソッドはLifecycleBaseに定義され、LifecycleBaseのinitとstartはinitInternalとstartInternalメソッドを呼び出し、この2つのメソッドはテンプレートメソッドであり、サブクラスによって具体的に実装されます.したがって、StandardServerのinitメソッドとstartメソッドを呼び出すと、StandardServer独自のinitInternalメソッドとstartInternalメソッドが実行され、各サービスのinitメソッドとstartメソッドがそれぞれループして呼び出されます.
InitInternalメソッドソース:
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 e)
              {
              }
              catch (IOException e)
              {
              }
        }
        cl = cl.getParent();
      }
    }

    for (int i = 0; i < this.services.length; i++)
      this.services[i].init();
  }

startInternalメソッドソース:
protected void startInternal()
    throws LifecycleException
  {
    fireLifecycleEvent("configure_start", null);
    setState(LifecycleState.STARTING);

    this.globalNamingResources.start();

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

StandardServerではawaitメソッドも実装されており,Catalinaではこのメソッドを呼び出してサービスを待機状態にする.
awaitメソッドソース:
public void await()
  {
//     -2,      ,     
    if (this.port == -2)
    {
      return;
    }
//     -1,        ,       break  ,     stop     true,  port=-1       stop        ,          
    if (this.port == -1) {
      try {
        this.awaitThread = Thread.currentThread();
        while (!this.stopAwait)
          try {
            Thread.sleep(10000L);
          }
          catch (InterruptedException ex) {
          }
      }
      finally {
        this.awaitThread = null;
      }
      return;
    }

    try
    {
//           ServerSocket
      this.awaitSocket = new ServerSocket(this.port, 1, InetAddress.getByName(this.address));
    }
    catch (IOException e) {
      log.error(new StringBuilder().append("StandardServer.await: create[").append(this.address).append(":").append(this.port).append("]: ").toString(), 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();
          InputStream stream;
          try { socket = serverSocket.accept();
            socket.setSoTimeout(10000);
            stream = socket.getInputStream();
          }
          catch (SocketTimeoutException ste)
          {
            log.warn(sm.getString("standardServer.accept.timeout", new Object[] { Long.valueOf(System.currentTimeMillis() - acceptStartTime) }), ste);
            try
            {
              if (socket != null)
                socket.close();
            }
            catch (IOException e) {
            }
            continue;
          }
          catch (AccessControlException ace)
          {
            log.warn(new StringBuilder().append("StandardServer.accept security exception: ").append(ace.getMessage()).toString(), ace);
            try
            {
              if (socket != null)
                socket.close();
            }
            catch (IOException e) {
            }
            continue;
          }
          catch (IOException e)
          {
            if (this.stopAwait)
            {
              try
              {
                if (socket != null)
                  socket.close();
              }
              catch (IOException e)
              {
              }
            }
            log.error("StandardServer.await: accept: ", e);
          }

          int expected = 1024;
          while (expected < this.shutdown.length()) {
            if (this.random == null)
              this.random = new Random();
            expected += this.random.nextInt() % 1024;
          }
          while (expected > 0) {
            int ch = -1;
            try {
              ch = stream.read();
            } catch (IOException e) {
              log.warn("StandardServer.await: read: ", e);
              ch = -1;
            }
//          ASCII   32 (ASCII 32       ),     32               
            if (ch < 32)
              break;
            command.append((char)ch);
            expected--;
          }
        }
        finally {
          try {
            if (socket != null) {
              socket.close();
            }
          }
          catch (IOException e)
          {
          }
        }
//                shudown    
        boolean match = command.toString().equals(this.shutdown);
        if (match) {
          log.info(sm.getString("standardServer.shutdownViaPort"));
          break;
        }
        log.warn(new StringBuilder().append("StandardServer.await: Invalid command '").append(command.toString()).append("' received").toString());
      }
    }
    finally
    {
      ServerSocket serverSocket;
      ServerSocket serverSocket = this.awaitSocket;
      this.awaitThread = null;
      this.awaitSocket = null;

      if (serverSocket != null)
        try {
          serverSocket.close();
        }
        catch (IOException e)
        {
        }
    }
  }

ポートportと閉じるコマンドshutdownはconf/serverです.xmlで構成された
ネットワークコマンドを使用してリレーショナル・サーバを使用しない場合は、ポートを-1に設定できます.
5、サービスの起動
Serviceのデフォルト実装はorg.apache.catalina.core.StandardServices、StandardServicesもLifecycleMBeanBaseクラスから継承されているため、initメソッドとstartメソッドは最終的にinitInternalメソッドとstartInternalメソッドを呼び出す
InitInternalメソッドソース:
protected void initInternal()
    throws LifecycleException
  {
    super.initInternal();

    if (this.container != null) {
      this.container.init();
    }

    for (Executor executor : findExecutors()) {
      if ((executor instanceof LifecycleMBeanBase)) {
        ((LifecycleMBeanBase)executor).setDomain(getDomain());
      }
      executor.init();
    }

    synchronized (this.connectors) {
      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);
        }
    }
  }

startInternalメソッドソース:
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();
      }

    }

    synchronized (this.connectors) {
      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);
        }
    }
  }

ソースコードから分かるように、この2つの方法は主にcontainer、executors、mapperListenerとconnectorsのinitとstart方法を呼び出し、mapperListenerはMapperのリスナーであり、containerコンテナの変換を傍受することができ、executorsはconnectorsでスレッドを管理するスレッドプールであり、serverである.xmlプロファイルには参照用がありますが、デフォルトはコメントで、コードは次のとおりです.

    
    

これによりConnectorはtomcatThreadPoolというスレッドプールを構成し,最大150スレッドを同時に起動でき,少なくとも4スレッドを起動しなければならない.
これでTomcatサービス全体が開始されます.
コメント:
ソースコードはcatalinaに由来する.jarで.
プロファイルはTomcatディレクトリの下のconf/serverに由来する.xml
参考資料:「Spring MVCソースコードの分析と実践を見抜く」