ソースコード分析からTomcatがサーブレットの初期化を呼び出す方法の詳細な説明

ソースコード分析からTomcatがサーブレットの初期化を呼び出す方法の詳細な説明

導入

前回のブログでは、Tomcat ソース コードをローカルで正常に実行したので、このブログでは、Tomcat が起動時にサーブレット コンテナーを初期化する方法をソース コード レベルから分析します。通常、サービスを Tomcat にデプロイし、構成ファイルを変更して起動し、外部にサービスを提供しますが、web.xml をロードする方法など、一部のプロセスについてはあまりよくわかっていません。これは、サーブレットと sringMVC を分析するために不可欠なプロセスです。

注釈ソースアドレス: https://github.com/good-jack/tomcat_source/tree/master

1. Tomcatを起動するコード

通常、Windows でも Linux でも、スクリプトを通じて Tomcat を起動しますが、これはソース コードを分析するのにあまり適していないため、コードを通じて起動する必要があります。起動コードは次のとおりです。

Tomcat の tomcat = new Tomcat();
        ポート番号を8080に設定します。
        //コンテナの各レイヤーを新規作成し、コンテナの各レイヤー間の関係を維持します。tomcat.addWebapp("/","/");
        tomcatを起動します。
        //リスニングポートをブロックします tomcat.getServer().await();

起動コードはまだ非常にシンプルです。コードから、このブログでは主に addWebapp() メソッドと start() メソッドを分析していることがわかります。これら 2 つのメソッドを通じて、サーブレット コンテナがいつ初期化されるかがわかります。

2. Tomcatフレームワーク

上記の 2 つの方法を分析する前に、Tomcat の基本的なフレームワークをまとめてみましょう。実際、私たちがよく知っている server.xml 構成ファイルから、Tomcat が一連の親子コンテナーで構成されていることがわかります。

サーバー ---> サービス --> コネクタ エンジン addChild---> コンテキスト (サーブレット コンテナ)。これらは、構成ファイルから分析したコンテナです。Tomcat が起動すると、コンテナはレイヤーごとに起動されます。

3. コンテナを作成する (addWebapp())

3.1 メソッド呼び出しフローチャート

上記のフローチャートは、ソース コードから徐々に分析されるいくつかの重要なメソッドを示しており、ソース コードを分析するのに非常に役立ちます。

3.2 ソースコード分析

1) リフレクションを通じてconfigContextリスナーを取得する

メソッド パス: package org.apache.catalina.startup.Tomcat.addWebapp(Host host, String contextPath, String docBase);

 
    パブリックコンテキストaddWebapp(ホストhost、文字列contextPath、文字列docBase) {
        //リフレクションを通じてリスナーContextConfigを取得します。
        //リフレクションで取得したものは、LifecycleListener の実装クラスである必要があります。実装クラスを取得するには、getConfigClass と入力します (org.apache.catalina.startup.ContextConfig)
        ライフサイクルリスナーリスナー = null;
        試す {
            クラス<?> clazz = Class.forName(getHost().getConfigClass());
            リスナー = (LifecycleListener) clazz.getConstructor().newInstance();
        } キャッチ (ReflectiveOperationException e) {
            // メソッドシグネチャを簡単に変更できないため、IAEでラップします。
            // 特定のチェック例外をスローする
            新しい IllegalArgumentException(e) をスローします。
        }
 
        addWebapp(ホスト、contextPath、docBase、リスナー) を返します。
    }

2) コンテキストコンテナを取得する (StandardContext)

次のコードでは、createContext() メソッドはリフレクションを通じて StandardContext コンテナをロードし、リスナー ContextConfig、ctx.addLifecycleListener(config); を設定します。

パブリックコンテキストaddWebapp(ホストhost、文字列contextPath、文字列docBase、
            ライフサイクルリスナー設定) {
 
        沈黙(ホスト、コンテキストパス);
 
        //コンテキスト コンテナ (StandardContext) を取得します。
        コンテキスト ctx = createContext(host, contextPath);
        ctx.setPath(コンテキストパス);
        ctx.setDocBase(docBase);
 
        (WebアプリにDefaultWebXmlを追加する){
            ctx.addLifecycleListener(getDefaultWebXmlListener());
        }
 
        ctx.setConfigFile(getWebappConfigFile(docBase, contextPath));
        //コンテキストにリスナーを追加します ctx.addLifecycleListener(config);
 
        if (addDefaultWebXmlToWebapp && (config instanceof ContextConfig)) {
            // 検索しないようにします (見つかった場合は重複エラーが発生します)
            ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath());
        }
 
        ホスト == null の場合 {
            //getHost はレイヤーごとにコンテナーを作成し、コンテナーの親子関係を維持します getHost().addChild(ctx);
        } それ以外 {
            ホストに子を追加します(ctx);
        }
 
        ctx を返します。
    }

3) あらゆるレベルでコンテナを管理する

getHost() メソッドは、各レイヤーのコンテナを取得し、サーバー コンテナやエンジン コンテナなどの親コンテナの関係を維持します。また、StandardContext コンテナは、getHost().addChild(ctx); を介して containerBase の addChild() メソッドを呼び出すことによって、子マップ内に維持されます。

  パブリックホストgetHost() {
        //各レイヤーに新しいコンテナを作成します。Engine engine = getEngine();
        (engine.findChildren().length>0)の場合{
            (ホスト) engine.findChildren()[0]を返します。
        }
 
        ホスト host = new StandardHost();
        ホスト名を設定します。
        //Tomcat 内の親子コンテナを維持します getEngine().addChild(host);
        ホストを返します。
    }

getEngine().addChild(host); メソッドは、親クラス containerBase の addChild メソッドを呼び出すことを選択します。

  @オーバーライド
    パブリック void addChild(コンテナの子) {
        (Globals.IS_SECURITY_ENABLED) の場合 {
            特権アクション<Void> dp =
                新しい PrivilegedAddChild(child);
            アクセスコントローラ.doPrivileged(dp);
        } それ以外 {
            //ここでの子パラメータはコンテキスト コンテナです addChildInternal(child);
        }
    }

addChildInternal() メソッドのコアコード

 プライベートvoid addChildInternal(コンテナの子) {
 
        デバッグが有効かどうかをチェックします
            log.debug("子要素 " + 子要素 + " " + this を追加します);
        同期(子) {
            (children.get(child.getName()) != null) の場合
                新しい IllegalArgumentException をスローします ("addChild: 子の名前 '" +
                                                   子.getName() +
                                                   "' は一意ではありません");
            child.setParent(this); // IAE がスローされる可能性がある
            子の名前を取得します。
    }

4. コンテナを起動する (tomcat.start())

4.1 メソッド呼び出しフローチャート

4.2 ソースコード分析

注: StandardServer、StandardService、StandardEngine およびその他のコンテナはすべて LifecycleBase を継承します。

これがテンプレートパターンの典型的な応用例です

1) コンテナをレイヤーごとに起動する

この時点でのサーバーは、先ほど作成したStandardServerに対応しています。

  パブリック void start() は LifecycleException をスローします {
        // サーバー コンテナーが作成されないようにする getServer();
        //コネクタ コンテナーを取得し、サービス コンテナーに設定します getConnector();
        //ここでの start の実装は LifecycleBase クラスで実装されています //LifecycleBase メソッドはテンプレート メソッドであり、Tomcat の起動プロセスで非常に重要です server.start();
    }

2) 開始方法を入力する

LifecycleBase に開始メソッドを入力します。コア メソッドは startInternal です。

上記から、StandardServerコンテナのstartInternal()メソッドを呼び出していることがわかります。そのため、ここではStandardServerを選択します。

メソッドパス: org.apache.catalina.core.StandardServer.startInternal()

保護されたvoid startInternal()はLifecycleExceptionをスローします{
 
        fireLifecycleEvent(CONFIGURE_START_EVENT、null);
        ライフサイクル状態を STARTING に設定します。
 
        グローバルネーミングリソースを開始します。
 
        // 定義したサービスを開始します
        同期 (servicesLock) {
            //サービスコンテナを起動します。Tomcat では複数のサービスコンテナを設定できます。各サービスコンテナは、(Service service : services) のサービスアプリケーションの 1 つに対応します。
                //StandardService.startInternal() に対応
                サービスを開始します。
            }
        }
    }

上記のコードから、サーバー コンテナーを起動するときに、サブコンテナーのサービス コンテナーを起動する必要があることがわかります。ここから、コンテナーはレイヤーごとに内側に爆発するため、次のステップでは、各レイヤーのコンテナーの star メソッドを順番に呼び出し始めます。ここでは詳細には触れません。

2) StandardContextコンテナが起動されるContainerBaseのstartInternal()メソッドのコアコード

 // 子コンテナがある場合は起動します
        //addWwbapp プロセスの addChild メソッドで追加されたので、ここで見つける必要があります //ここで見つけるのはコンテキスト コンテナーです Container children[] = findChildren();
        List<Future<Void>> 結果 = new ArrayList<>();
        for (コンテナの子: children) {
            //スレッドプールを非同期的に開始してコンテキストコンテナを開始し、新しいStartChildに入ります
            結果を追加します(startStopExecutor.submit(新しい StartChild(child)));
        }

新しいStartChild(child)メソッドはStandardContextコンテナを起動します

    プライベート静的クラス StartChild は Callable<Void> を実装します {
 
        プライベートコンテナの子。
 
        パブリック StartChild(コンテナの子) {
            this.child = 子;
        }
 
        @オーバーライド
        パブリック Void call() は LifecycleException をスローします {
            //コンテキストの開始を開始します。実際には StandardContext.startInternal() を呼び出します。
            子.start();
            null を返します。
        }
    }

StandardContext.startInternal() メソッドのコア コード:

   保護されたvoid fireLifecycleEvent(文字列型、オブジェクトデータ) {
        LifecycleEvent イベント = 新しい LifecycleEvent(this、type、data);
        //lifecycleListeners addwebappメソッドの最初のステップで、リスニングcontextConfigオブジェクトを設定します。(LifecycleListener listener : lifecycleListeners) {
            //contextConfig の lifecycleEvent() メソッドはここで呼び出されます listener.lifecycleEvent(event);
        }
    }

contextConfigにlifecycleEvent()メソッドを入力する

パブリック void lifecycleEvent(LifecycleEvent イベント) {
 
        // 関連付けられているコンテキストを識別する
        試す {
            コンテキスト = (コンテキスト) event.getLifecycle();
        } キャッチ (ClassCastException e) {
            ログエラー(sm.getString("contextConfig.cce", event.getLifecycle()), e);
            戻る;
        }
 
        // 発生したイベントを処理する
        イベントの getType() が Lifecycle.CONFIGURE_START_EVENT と等しい場合
            //web.xml のコンテンツ解析を完了します。configureStart();
        } そうでない場合 (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            開始する前に();
        } そうでない場合 (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
            // 管理ツールの docBase を復元します
            (元のドキュメントベースが null ではない場合){
                コンテキスト.setDocBase(元のDocBase);
            }
        } そうでない場合 (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
            停止を設定します。
        } そうでない場合 (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
            初期化();
        } そうでない場合 (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
            破壊する();
        }
 
    }

上記の方法では、web.xml が読み込まれて解析され、xml で構成されたサーブレットが読み込まれてラッパー オブジェクトにカプセル化されます。

3) サーブレットコンテナを起動し、StandardContext.startInternal() の loadOnStartup(findChildren()) メソッドを実行します。

パブリックブールloadOnStartup(コンテナの子[]) {
 
        // 初期化が必要な「起動時にロード」サーブレットを収集します
        TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
        for (コンテナの子: children) {
            //ここでのラッパーは先ほどカプセル化したサーブレットです
            ラッパー wrapper = (ラッパー) child;
            int loadOnStartup = wrapper.getLoadOnStartup();
            起動時にロードが0の場合
                続く;
            }
            整数キー = Integer.valueOf(loadOnStartup);
            ArrayList<ラッパー> list = map.get(key);
            if (リスト == null) {
                リスト = 新しいArrayList<>();
                map.put(キー、リスト);
            }
            リストに追加(ラッパー);
        }
 
        // 収集された「起動時にロード」サーブレットをロードします
        (ArrayList<Wrapper> リスト: map.values()) の場合 {
            for (ラッパー wrapper : リスト) {
                試す {
                    //load メソッドは最終的にサーブレットの init メソッド wrapper.load() を呼び出します。
                } キャッチ (ServletException e) {
                    getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
                          getName()、wrapper.getName())、StandardWrapper.getRootCause(e));
                    // 注意: ロードエラー(エラーをスローするサーブレットを含む)
                    // init() メソッドからの UnavailableException は発生しません
                    // アプリケーションの起動に致命的
                    // failCtxIfServletStartFails="true" が指定されていない場合
                    if(getComputedFailCtxIfServletStartFails()) {
                        false を返します。
                    }
                }
            }
        }
        true を返します。
 
    }

load メソッドは最終的にサーブレットの init メソッドを呼び出します。

V. 結論

上記の内容は、Tomcat 全体がサーブレット初期化メソッドを呼び出すプロセスです。これがプロセス全体についての私の理解です。誤りがあれば、ご指摘ください。ソース コードの重要な部分には注釈を付けましたので、必要な場合は注釈付きのソース コードをダウンロードできます。注釈付きのソース コードのアドレスは次のとおりです。

https://github.com/good-jack/tomcat_source/tree/master

これで、ソースコードから tomcat を解析して Servlet 初期化を呼び出す方法についての説明は終了です。 tomcat が Servlet 初期化を呼び出す方法の詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。 今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • IDEA2021 tomcat10 サーブレットの新しいバージョンの落とし穴
  • Tomcat でのサーブレットの作成と実装に関する深い理解
  • Tomcat が非同期サーブレットを実装する方法の詳細な説明
  • サーブレットとTomcat_PowerNode Javaアカデミー
  • Tomcat で非同期サーブレットを実装する方法
  • Tomcat のサーブレット オブジェクト プールの紹介と使用
  • Tomcat におけるサーブレットの動作メカニズムの詳細な紹介

<<:  UTF8 でエンコードされた Web ページにファイルが含まれている場合の、ページの前の空白行の解決方法

>>:  vue3を使用して人間と猫のコミュニケーションアプレットを実装する

推薦する

コンピュータが予期せずシャットダウンした後、VMware で Linux がインターネットに接続できない問題の解決策

問題の説明: Linux システムのネットワーク カード ファイル /etc/sysconfig/n...

jQueryメソッド属性の詳細な説明

目次1. jQueryの紹介2. jQueryセレクター2.1 5つの基本セレクタ2.2 4種類の関...

マークアップ言語 - Web アプリケーション CSS スタイル

123WORDPRESS.COM HTML チュートリアル セクションに戻るには、ここをクリックして...

vue構成ファイルはルーティングとメニューインスタンスコードを自動的に生成します

目次前面に書かれたルータ.jsonルート生成メニュー生成効果要約する前面に書かれたルートを繰り返し記...

Linux のスクリーンコマンドとその使い方

画面紹介Screen は、コマンドライン端末切り替え用に GNU プロジェクトによって開発されたフリ...

linuxdeployqt を使用して Ubuntu で Qt プログラムをパッケージ化する問題を解決する

いくつかの Qt インターフェース プログラムを作成しましたが、Qt 環境がインストールされていない...

JSONObject の使用方法の詳細な説明

JSONObject は単なるデータ構造であり、JSON 形式のデータ構造 ( key-value構...

MySQLスレーブのメンテナンスに関する経験の共有

序文: MySQL マスター/スレーブ アーキテクチャは、最も一般的に使用されるアーキテクチャ セッ...

MySQL 自動インクリメント ID のオーバーサイズ問題のトラブルシューティングと解決策

導入Xiao A がコードを書いていたところ、DBA Xiao B が突然、「急いでユーザー固有情報...

CSSとHTMLを組み合わせる4つの方法

(1)各HTMLタグには属性スタイルがあり、CSSとHTMLを組み合わせている。 <div s...

MySQL の完全バックアップとクイックリカバリ方法

過去 15 日間のデータをバックアップするシンプルな MySQL 完全バックアップ スクリプト。バッ...

Vue-CLI マルチページディレクトリパッケージ化手順の記録

ページディレクトリ構造 デフォルトの HTML テンプレート ファイル public/index.h...

テーブルの動的な色の変更を実現するJavaScript

この記事では、テーブルの動的な色の変更を実現するためのJavaScriptの具体的なコードを参考まで...

CSS でホバー ドロップダウン メニューを実装する方法

いつものように、今日は非常に実用的な CSS 効果についてお話します。マウスがボタンに移動すると、ド...

divコンテナ内の背景色または画像は、サイズが大きくなるにつれて大きくなります。

コードをコピーコードは次のとおりです。高さ:自動 !重要;高さ:550px;最小高さ:550px; ...