ソースコード分析から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を使用して人間と猫のコミュニケーションアプレットを実装する

推薦する

JavaScript クロージャの説明

目次1. クロージャとは何ですか? 2. クロージャの役割2.1) メモリ2.2) プライベート変数...

MySQL 5.7.17 圧縮パッケージのインストール不要の構成プロセス図

MySQL データベース管理ソフトウェアには、エンタープライズ エディションとコミュニティ エディシ...

よくある HTML タグの記述エラー

HTML Police がコードを調べて意味のないタグをすべて見つけ出すので、注意を払う必要がありま...

表示しているページのスナップショットを Baidu が保存できないように設定する方法

今日、Baidu でページを検索したところ、ページが削除されていたため、当然 Baidu スナップシ...

JSは円形のプログレスバーのドラッグとスライドを実装します

この記事の例では、円形のプログレスバーのドラッグアンドスライドを実現するための具体的なJSコードを紹...

IDEA を使用して Web プロジェクトを作成し、Tomcat に公開する方法

目次ウェブ開発1. Web開発の概要Tomcatのインストールと設定Tomcatをインストールする2...

Vue Element フロントエンドアプリケーション開発: Vuex での API ストアビューの使用

目次概要1. フロントエンドとバックエンドの分離とWeb APIの優先ルート設計2. Axiosネッ...

Vueはvue-quill-editorリッチテキストエディタを使用し、画像をサーバーにアップロードします。

目次1. 準備2. グローバルコンポーネント quill-editor を定義する1. テンプレート...

js を使用してシンプルなスイッチ ライト コードを実装する

体の部位: <button>ライトのオン/オフを切り替える</button>...

Vueは、サイドナビゲーションバーをタブページに関連付けるサンプルコードを実装します。

目次テクノロジースタック効果分析するテクノロジースタックサイドバー用Antdtabは要素を使用します...

Linuxはバイナリモードを使用してmysqlをインストールします

この記事では、LinuxにバイナリモードでMySQLをインストールする具体的な手順を参考までに紹介し...

最適なウェブページ幅とその互換性のある実装方法

1. Web ページをデザインするときに、幅を決定するのは非常に面倒な作業です。 jb51.net ...

RGBAアルファ透明度変換計算表

IEでのRGBAとフィルター値の変換RGBA 透明度値IE フィルター値0.1 19 0.2 33 ...

MySql5.7.21 インストールポイント記録メモ

ダウンロードしたバージョンは、Zip 解凍版、Windows システムです。長い間 Windows ...

最新のmysql-5.7.21のインストールと設定方法

1. ダウンロードしたMySQLの圧縮パッケージをインストールディレクトリに解凍します。 2. 新し...