導入前回のブログでは、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 をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: UTF8 でエンコードされた Web ページにファイルが含まれている場合の、ページの前の空白行の解決方法
>>: vue3を使用して人間と猫のコミュニケーションアプレットを実装する
問題の説明: Linux システムのネットワーク カード ファイル /etc/sysconfig/n...
目次1. jQueryの紹介2. jQueryセレクター2.1 5つの基本セレクタ2.2 4種類の関...
123WORDPRESS.COM HTML チュートリアル セクションに戻るには、ここをクリックして...
目次前面に書かれたルータ.jsonルート生成メニュー生成効果要約する前面に書かれたルートを繰り返し記...
画面紹介Screen は、コマンドライン端末切り替え用に GNU プロジェクトによって開発されたフリ...
いくつかの Qt インターフェース プログラムを作成しましたが、Qt 環境がインストールされていない...
JSONObject は単なるデータ構造であり、JSON 形式のデータ構造 ( key-value構...
序文: MySQL マスター/スレーブ アーキテクチャは、最も一般的に使用されるアーキテクチャ セッ...
導入Xiao A がコードを書いていたところ、DBA Xiao B が突然、「急いでユーザー固有情報...
(1)各HTMLタグには属性スタイルがあり、CSSとHTMLを組み合わせている。 <div s...
過去 15 日間のデータをバックアップするシンプルな MySQL 完全バックアップ スクリプト。バッ...
ページディレクトリ構造 デフォルトの HTML テンプレート ファイル public/index.h...
この記事では、テーブルの動的な色の変更を実現するためのJavaScriptの具体的なコードを参考まで...
いつものように、今日は非常に実用的な CSS 効果についてお話します。マウスがボタンに移動すると、ド...
コードをコピーコードは次のとおりです。高さ:自動 !重要;高さ:550px;最小高さ:550px; ...