1. 学習目標1.1. Tomcatアーキテクチャの設計と原則をマスターして社内スキルを向上させるマクロビュー Tomcat は、「 顕微鏡写真
現在普及しているマイクロサービスもこの考え方に従っており、モノリシック アプリケーションを機能に応じて「マイクロサービス」に分割します。分割プロセス中に共通点が抽出され、これらの共通点がコアとなる基本サービスまたは汎用ライブラリになります。 「中間プラットフォーム」の考え方についても同様です。 デザイン パターンは、変更をカプセル化するための強力なツールとなることがよくあります。デザイン パターンを適切に使用することで、コードとシステム設計をエレガントですっきりしたものにすることができます。 これは、優れたオープンソースソフトウェアを学ぶことで得られる「内なる力」であり、決して古くなることのない、そこに含まれる設計思想や哲学こそが根本的な道なのです。彼らの設計経験から学び、設計パターンを適切に使用して変更と定数をカプセル化し、ソース コードからの経験を活用して独自のシステム設計能力を向上させます。 1.2. リクエストがSpringにどのように接続されるかについてのマクロの理解仕事の過程で、私たちはすでに Java 構文に精通しており、いくつかのデザイン パターンを「記憶」し、多くの Web フレームワークを使用してきましたが、実際のプロジェクトでそれらを使用する機会はほとんどありません。システムを独自に設計することは、ニーズに応じて一度に 1 つのサービスを実装するだけのように思えます。私の頭の中には、Java Web 開発の全体像があまりないようです。たとえば、ブラウザのリクエストが Spring のコードとどのように関連しているかがわかりません。 このボトルネックを打破するために、大企業の肩に乗って優れたオープンソース システムを学び、大企業がこれらの問題に対してどのように考えているかを見てみてはいかがでしょうか。 Tomcat の原理を勉強した結果、 1.3. システム設計能力の向上Tomcat を学習しているときに、Java マルチスレッド並行プログラミング、ソケット ネットワーク プログラミング、リフレクションなどの高度な Java テクノロジを多数使用していることにも気付きました。以前は、これらのテクノロジーについて知っていて、面接のためにいくつかの質問を暗記しただけでした。しかし、「知っている」ことと「使える」ことの間にはギャップがあるといつも感じています。Tomcat のソース コードを研究することで、これらのテクノロジをどのようなシナリオで使用するかを学びました。 また、インターフェース指向プログラミング、コンポーネントベースの組み合わせモード、スケルトン抽象クラス、ワンクリック開始と停止、オブジェクトプールテクノロジー、テンプレートメソッド、オブザーバーモード、責任連鎖モードなどのさまざまな設計パターンなどのシステム設計機能もあります。その後、私はそれらを真似して、これらの設計アイデアを実際の作業に適用し始めました。 2. 全体的なアーキテクチャ設計今日は、Tomcat の設計思想を段階的に分析します。一方では、Tomcat の全体的なアーキテクチャを学び、マクロの観点から複雑なシステムを設計する方法、トップレベル モジュールを設計する方法、モジュール間の関係を学ぶことができます。他方では、Tomcat の動作原理を詳細に研究するための基礎も築かれます。 Tomcat の起動プロセス:
Tomcat は次の 2 つのコア機能を実装します。
したがって、Tomcat はコネクタとコンテナという 2 つのコア コンポーネントで設計されています。コネクタは外部通信を担当し、コンテナは内部処理を担当します。 複数の
各コンポーネントには対応するライフサイクルがあり、起動する必要があり、その内部サブコンポーネントも起動する必要があります。たとえば、Tomcat インスタンスにはサービスが含まれ、サービスには複数のコネクタとコンテナが含まれます。コンテナには複数のホストが含まれ、ホスト内には複数のコンテキスト コンテナが存在する可能性があり、コンテキストには複数のサーブレットが含まれる可能性もあります。そのため、Tomcat は複合モードを使用して各コンポーネントを管理し、各コンポーネントを単一のグループとして扱います。全体的に、各コンポーネントのデザインは「ロシア人形」のようです。 2.1 コネクタコネクタについて説明する前に、まず
Tomcat でサポートされているアプリケーション層プロトコルは次のとおりです。
したがって、1 つのコンテナーが複数のコネクタとドッキングする可能性があります。コネクタは、ネットワーク プロトコルと 改良されたコネクタの機能要件は次のとおりです。
要件が明確にリストされた後、次に検討する必要があるのは、コネクタにどのようなサブモジュールが必要かということです。優れたモジュール設計では、高い凝集性と低い結合性を考慮する必要があります。
コネクタには、次の 3 つの非常にまとまりのある機能を実行する必要があることがわかりました。
そのため、Tomcat の設計者は、これらの 3 つの機能を実装するために、 ネットワーク通信の I/O モデルは変化しており、アプリケーション層プロトコルも変化していますが、全体的な処理ロジックは変更されていません。 2.2 カプセル化の変更と不変性そのため、Tomcat はこれらの安定した部分をカプセル化するために一連の抽象基本クラスを設計しました。抽象基本クラス これはテンプレート メソッド デザイン パターンの応用です。 要約すると、コネクタの 3 つのコア コンポーネントである ProtocolHandler コンポーネント: 主にネットワーク接続とアプリケーション層プロトコルを処理します。エンドポイントとプロセッサという 2 つの重要なコンポーネントが含まれています。この 2 つのコンポーネントが組み合わさって ProtocoHandler が形成されます。その動作原理を詳しく紹介します。 終点: アクセプターはソケット接続要求を監視するために使用されます。 Java マルチプレクサの使用は、次の 2 つのステップで済むことがわかっています。
Tomcat では、 LimitLatch は、最大接続数を制御する接続コントローラです。NIO モードのデフォルト値は 10,000 です。このしきい値に達すると、接続要求は拒否されます。 SocketProcessor は Runnable インターフェイスを実装します。このインターフェイスでは、run メソッド内の ワークフローは次のとおりです。 プロセッサ: プロセッサは、HTTP プロトコルを実装するために使用されます。プロセッサは、エンドポイントからソケットを受信し、バイト ストリームを読み取って Tomcat のリクエストおよびレスポンス オブジェクトに解析し、アダプタを介して処理するためにコンテナに送信します。プロセッサは、アプリケーション層プロトコルの抽象化です。 図から、EndPoint がソケット接続を受け取った後、SocketProcessor タスクを生成し、それをスレッド プールに送信して処理することがわかります。SocketProcessor の Run メソッドは、HttpProcessor コンポーネントを呼び出して、アプリケーション層プロトコルを解析します。Processor は解析によって Request オブジェクトを生成した後、Adapter の Service メソッドを呼び出します。このメソッドは、次のコードを通じてコンテナーに要求を渡します。 // コンテナを呼び出す コネクタ.getService().getContainer().getPipeline().getFirst().invoke(リクエスト、レスポンス); アダプタコンポーネント: プロトコルが異なるため、Tomcat はリクエスト情報を格納するための独自の Tomcat 設計者の解決策は、アダプタ パターンの典型的なアプリケーションである 2.3 コンテナコネクタは外部通信を担当し、コンテナは内部処理を担当します。具体的には、コネクタはソケット通信とアプリケーション層プロトコルの分析を処理して コンテナ: 名前が示すように、ものを保持するために使用されるため、Tomcat コンテナは Tomcat は、 次の図に示すように、これら 4 つのコンテナーは並列関係ではなく、親子関係にあることに注意してください。 なぜこれほど多くのレベルのコンテナを設計する必要があるのかと疑問に思うかもしれません。複雑さが増すのではないですか?実際、この背後にある考慮事項は、Tomcat が階層化アーキテクチャを使用して、サーブレット コンテナーを非常に柔軟にすることです。ここでは、1つのホストに複数のコンテキストがあり、1つのコンテキストにも複数のサーブレットが含まれており、各コンポーネントには統一されたライフサイクル管理が必要であるため、結合モードではこれらのコンテナを設計します。 Tomcat 構成ファイルを使用すると、階層関係をより深く理解することができます。 <Server port="8005" shutdown="SHUTDOWN"> // トップレベル コンポーネント。複数のサービスを含めることができ、Tomcat インスタンスを表します。<Service name="Catalina"> // トップレベル コンポーネント。エンジン、複数のコネクタが含まれます。<Connector port="8080" protocol="HTTP/1.1" 接続タイムアウト = "20000" リダイレクトポート="8443" /> <!-- ポート 8009 で AJP 1.3 コネクタを定義します --> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> // コネクタ // コンテナ コンポーネント: エンジンは複数のホストを含むすべてのサービス要求を処理します <エンジン名="Catalina" defaultHost="localhost"> //コンテナコンポーネント: 指定されたホストの下でクライアント要求を処理します。複数のコンテキストを含めることができます。 <ホスト名="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> //コンテナ コンポーネント: 特定のコンテキスト Web アプリケーションのすべてのクライアント要求を処理します <Context></Context> </ホスト> </エンジン> </サービス> </サーバー> これらのコンテナをどのように管理すればよいでしょうか?コンテナ間に親子関係があり、ツリー構造になっていることがわかりました。デザインパターンで組み合わせパターンを考えることは可能でしょうか? Tomcat はこれらのコンテナを管理するために結合モードを使用します。具体的な実装方法は、すべてのコンテナ コンポーネントが パブリックインターフェースコンテナはライフサイクルを拡張します { パブリック void setName(文字列名); パブリック コンテナ getParent(); パブリック void setParent(コンテナー コンテナー); パブリック void addChild(コンテナの子); パブリック void removeChild(コンテナの子); パブリック コンテナ findChild(文字列名); } これまでに、 2.4. サーブレットの検索を要求するプロセスリクエストは、どの リクエストが届くと、 ユーザーが図の 1. まず、プロトコルとポート番号に基づいてサービスとエンジンを決定します。 Tomcat のデフォルトの HTTP コネクタはポート 8080 をリッスンし、デフォルトの AJP コネクタはポート 8009 をリッスンします。上記の例の URL はポート 8080 にアクセスするため、リクエストは HTTP コネクタによって受信され、コネクタはサービス コンポーネントに属しているため、サービス コンポーネントが決定されます。また、複数のコネクタに加えて、サービス コンポーネントにはコンテナー コンポーネント、具体的にはエンジン コンテナーもあるため、サービスが決定されるとエンジンも決定されることもわかっています。 2. ドメイン名に基づいてホストを選択します。サービスとエンジンが決定された後、Mapper コンポーネントは URL 内のドメイン名を通じて対応する Host コンテナを検索します。たとえば、例の URL によってアクセスされるドメイン名は 3. URL パスに基づいてコンテキスト コンポーネントを見つけます。ホストが決定された後、Mapper は URL パスに従って対応する Web アプリケーションのパスを照合します。たとえば、この例では、アクセスされたパスは /order なので、コンテキスト コンテナ Context4 が見つかります。 4. URL パスに基づいてラッパー (サーブレット) を見つけます。コンテキストが決定された後、マッパーは web.xml で構成されたサーブレット マッピング パスに従って特定のラッパーとサーブレットを検索します。 コネクタ内のアダプタはコンテナのサービス メソッドを呼び出してサーブレットを実行します。リクエストを受信する最初のコンテナはエンジン コンテナです。エンジン コンテナはリクエストを処理した後、リクエストを子コンテナのホストに渡してさらに処理します。最後に、リクエストはラッパー コンテナに渡され、ラッパーは処理のために最終的なサーブレットを呼び出します。では、この呼び出しプロセスはどのように実装されるのでしょうか?答えは、パイプラインバルブパイプラインを使用することです。 パブリックインターフェースValve { パブリック Valve getNext(); public void setNext(Valve バルブ); public void invoke(Request リクエスト、Response レスポンス) } パイプラインインターフェースを引き続き見てみましょう パブリックインターフェースパイプライン{ public void addValve(Valve バルブ); パブリック Valve getBasic(); public void setBasic(Valve バルブ); パブリック Valve getFirst(); } 実際、各コンテナには Pipeline オブジェクトがあります。この Pipeline の最初のバルブがトリガーされる限り、このコンテナの これは、 プロセス全体はコネクタ内の @オーバーライド パブリック void サービス (org.apache.coyote.Request req、org.apache.coyote.Response res) { // 他のコードは省略 // コンテナの呼び出し コネクタ.getService().getContainer().getPipeline().getFirst().invoke() リクエスト、レスポンス); ... } ラッパー コンテナの最後のバルブはフィルター チェーンを作成し、 以前、
ライフサイクル 先ほど、 コンポーネントの作成、初期化、開始、停止、破棄を統一的に管理するにはどうすればよいでしょうか?コードロジックを明確にするにはどうすればよいでしょうか?コンポーネントを簡単に追加または削除するにはどうすればよいですか?コンポーネントが漏れや重複なく開始および停止されることをどのように確認すればよいでしょうか? ワンタッチスタートとストップ:LifeCycleインターフェース 設計とは、システムの変化する点と不変の点を見つけることです。ここで不変の点は、各コンポーネントが作成、初期化、および起動のプロセスを経る必要があり、これらの状態と状態変換は変更されないままであるということです。変更点は、各特定コンポーネントの初期化方法、つまり起動方法が異なることです。 そのため、Tomcat は不変ポイントをライフサイクルに関連する LifeCycle と呼ばれるインターフェースに抽象化します。 LifeCycle インターフェースは、 親コンポーネントの スケーラビリティ: ライフサイクル イベント システムのスケーラビリティという別の問題について考えてみましょう。各コンポーネントの コンポーネントの 再利用性: LifeCycleBase 抽象基本クラス 抽象テンプレート設計パターンをもう一度確認してください。 インターフェースを使用する場合は、クラスを使用してインターフェースを実装する必要があります。一般的に、実装クラスは複数存在し、異なるクラスでもインターフェースを実装する際に同じロジックがいくつか存在することがよくあります。各サブクラスで実装する必要がある場合、コードが重複することになります。サブクラスはこのロジックをどのように再利用できるでしょうか?実際には、共通のロジックを実装するための基本クラスを定義し、各サブクラスにそれを継承させることで再利用の目的を達成します。 Tomcat は、LifeCycle インターフェイスを実装するために基本クラス LifeCycleBase を定義し、ライフ ステートの遷移と維持、ライフ イベントのトリガー、リスナーの追加と削除などの共通ロジックを基本クラスに組み込みます。一方、サブクラスは、独自の初期化、開始、停止メソッドを実装する役割を担います。 パブリック抽象クラス LifecycleBase は Lifecycle を実装します{ //すべてのオブザーバーを保持 private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>(); /** * イベントを公開 * * @param type イベントタイプ * @param data イベントに関連付けられたデータ。 */ 保護されたvoid fireLifecycleEvent(文字列型、オブジェクトデータ) { LifecycleEvent イベント = 新しい LifecycleEvent(this、type、data); (LifecycleListener リスナー: lifecycleListeners) { リスナー.lifecycleEvent(イベント); } } // テンプレートメソッドは起動プロセス全体を定義し、すべてのコンテナを起動します @Override パブリック最終同期void init()はLifecycleExceptionをスローします{ //1. ステータスチェック if (!state.equals(LifecycleState.NEW)) { 無効な遷移(Lifecycle.BEFORE_INIT_EVENT); } 試す { //2. INITIALIZING イベントのリスナーをトリガーします。setStateInternal(LifecycleState.INITIALIZING, null, false); // 3. 特定のサブクラスの初期化メソッド initInternal() を呼び出します。 // 4. INITIALIZED イベントのリスナーをトリガーします。setStateInternal(LifecycleState.INITIALIZED, null, false); } キャッチ (Throwable t) { 例外をスローする LifecycleState.FAILED を null に設定し、false を設定します。 新しいライフサイクル例外をスローします( sm.getString("lifecycleBase.initFail",toString()), t); } } } ワンクリックの開始と停止、およびエレガントなライフサイクル管理を実現するために、Tomcat はスケーラビリティと再利用性を考慮し、オブジェクト指向の考え方と設計パターンを極限まで取り入れています。 親子関係のある多くのエンティティを維持する必要がある場合は、複合パターンの使用を検討してください。 オブザーバーのパターンは「ハイエンド」に聞こえますが、実際には、イベントが発生したときに一連の更新操作を実行する必要があることを意味します。低カップリング、非侵入通知および更新メカニズムが実装されています。 3.なぜTomcatが親の委任メカニズムを破るのか3.1 パブリック Class<?> loadClass(String name) は ClassNotFoundException をスローします { loadClass(name, false) を返します。 } 保護されたクラス<?> loadClass(文字列名、ブール値解決) ClassNotFoundException をスローします { 同期化 (getClassLoadingLock(名前)) { // クラスがロードされているかどうかを確認します Class<?> c = findLoadedClass(name); // ロードされていない場合 if (c == null) { //親ローダーに委任してロードするには、if(parent!= null){を再帰的に呼び出します。 c = parent.loadclass(name、false); } それ以外 { //親ローダーが空の場合は、ブートストラップがロードされているかどうかを見つけてくださいc = findbootstrapclassornull(name); } //それでもロードできない場合は、独自のFindClassに電話してIF(c == null){ クラス名を検索します。 } } if(resolve){ Resolveclass(c); } c を返します。 } } 保護されたクラス<?> findClass(文字列名){ //1. 渡されたクラス名に従って、特定のディレクトリ内のクラス ファイルを検索し、.class ファイルをメモリに読み込みます... //2. バイト配列を Class オブジェクトに変換するには、defineClass を呼び出します。 return defineClass(buf, off, len); } // バイトコード配列をクラスオブジェクトに解析し、ネイティブメソッドで実装します。protected final Class<?> defineClass(byte[] b, int off, int len){ ... } JDKには3つのクラスローダーがあり、クラスのローダーをカスタマイズすることもできます。
これらのクラス ローダーの動作原理は同じですが、違いはロード パスが異なること、つまり 3.2 Tomcatは基本的に、バックグラウンドスレッドを介して定期的なタスクを実行し、クラスファイルの変更を定期的に検出し、変更が見つかった場合はクラスをリロードします。 保護されたクラスコンテナバックグラウンドプロセッサは実行可能{ @オーバーライド パブリックボイド実行() { //ここで渡されたパラメーターは、「ホストクラス」ProcessChildren(containerbase.this)のインスタンスであることに注意してください。 } 保護されたボイドプロセスチャイルド(コンテナコンテナ){ 試す { //現在のコンテナの背景プロセスメソッドを呼び出します container.backgroundProcess(); // 2すべての子容器を通過し、再帰的にProcessChildrenを呼び出します //このようにして、現在のコンテナのすべての子孫は処理されたコンテナ[] children = container.findchildren(); for(int i = 0; i <children.length; i ++){ //ここでは、コンテナベースクラスにはbackgroundprocessordelayと呼ばれる変数があることに注意してください。 if(children [i] .getBackGroundProcessordelay()<= 0){ Processchildren(子供[i]); } } } catch(throwable t){...} Tomcatのホットロードは、主にコンテキストコンテナのリロードメソッドを呼び出すことにより、コンテキストコンテナに実装されます。マクロの観点から詳細を脇に置いて、主なタスクは次のとおりです。
このプロセスでは、クラスローダーが重要な役割を果たします。コンテキストコンテナは、クラスローダーが破壊されると、ロードされたすべてのクラスも破壊されます。起動プロセス中に、コンテキストコンテナは新しいクラスファイルをロードする新しいクラスローダーを作成します。 3.3 TomcatのClass Loader Classメソッドを見つけます
理解と読書を容易にするために、私はいくつかの詳細を削除しました: パブリック Class<?> findClass(String name) は ClassNotFoundException をスローします { ... クラス<?> clazz = null; 試す { //1. まず、Web アプリケーション ディレクトリでクラスを検索します。clazz = findClassInternal(name); } キャッチ (RuntimeException e) { eを投げる; } (clazz == null)の場合{ 試す { //2. ローカル ディレクトリで見つからない場合は、親ローダーで検索させます。clazz = super.findClass(name); } キャッチ (RuntimeException e) { eを投げる; } //3. 親クラスが見つからない場合は、ClassNotFoundException をスローします。 (clazz == null)の場合{ 新しい ClassNotFoundException(名前) をスローします。 } 戻りクラッズ; } 1. Webアプリケーションのローカルディレクトリにロードされるクラスを最初に検索します。 2。それが見つかっていない場合 3.しかし、親のローダーはこのクラスを見つけず、 LoadClassメソッド Tomcatクラスローダーの パブリック Class<?> loadClass(String name, boolean resolve) は ClassNotFoundException をスローします { 同期化 (getClassLoadingLock(名前)) { クラス<?> clazz = null; // 1クラスがローカルキャッシュにロードされているかどうかを確認します。 (clazz != null)の場合{ もし(解決する) クラスを解決します(clazz); 戻りクラッズ; } // 2。 (clazz != null)の場合{ もし(解決する) クラスを解決します(clazz); 戻りクラッズ; } // 3。extclassloaderクラスローダークラスでロードしてみてください、なぜですか? クラスローダー javaseLoader = getJavaseClassLoader(); 試す { clazz = javaseLoader.loadClass(名前); (clazz != null)の場合{ もし(解決する) クラスを解決します(clazz); 戻りクラッズ; } } キャッチ (ClassNotFoundException e) { // 無視する } //4。ローカルディレクトリでクラスを検索して試してみてください{ clazz = findClass(名前); (clazz != null)の場合{ もし(解決する) クラスを解決します(clazz); 戻りクラッズ; } } キャッチ (ClassNotFoundException e) { // 無視する } //5。Try{システムクラスローダー(つまり、AppClassLoader)を使用してください{ clazz = Class.forName(名前、false、親); (clazz != null)の場合{ もし(解決する) クラスを解決します(clazz); 戻りクラッズ; } } キャッチ (ClassNotFoundException e) { // 無視する } } //6. 上記のプロセスはすべてロードに失敗し、例外をスローします。throw new ClassNotFoundException(name); } 6つの主な手順があります。 1.最初に、クラスがローカルキャッシュにロードされているかどうか、つまりTomcatのクラスローダーがこのクラスをロードしたかどうかを確認します。 2. Tomcatクラスローダーがこのクラスをロードしていない場合は、システムクラスローダーがロードされているかどうかを確認します。 3.何もない場合は、このステップがより重要であり、WebアプリケーションがコアJREクラスを上書きするのを防ぐことです。 Tomcatは親の委任メカニズムを破る必要があるため、オブジェクトと呼ばれるクラスがWebアプリケーションでカスタマイズされている場合、オブジェクトクラスが最初にロードされます。 4. 5.ローカルディレクトリにそのようなクラスがない場合、それはWebアプリケーション自体によって定義されるクラスではないことを意味します。システムクラスローダーによってロードされます。 6.上記のすべての読み込みプロセスが失敗した場合は、 3.4 Tomcatは 1. Tomcatで2つのWebアプリケーションを実行すると、2つのWebアプリケーションに同じ名前の 2。両方 3. JVMと同様に、Tomcat自体のクラスとWebアプリケーションのクラスを分離する必要があります。 1。WebAppClassLoader Tomcatのソリューションは、クラスローダー 2。SharedClassloader 重要な要件は、2つのWebアプリケーション間でライブラリクラスを共有する方法であり、同じクラスを繰り返しロードすることはできません。親代表団のメカニズムでは、各チャイルドローダーは親ローダーを介してクラスをロードできます。そのため、親ローダーのロードパスで共有する必要があるクラスを配置するだけでは十分ではありませんか? したがって、Tomcatのデザイナーは、特にWebアプリケーション間で共有されるクラスをロードするために、 3。Catalinaclassloader Tomcat自体のクラスをWebアプリケーションのクラスから分離する方法は? 共有するには、父と息子の関係を経て、隔離するには兄弟関係が必要です。同胞団は、2つのクラスローダーが並行していることを指し、これに基づいて同じ親のローダー このデザインには問題があります。 古い方法は、 4。全体的なアーキテクチャの設計分析と利益の要約Tomcatの全体的なアーキテクチャの以前の学習を通じて、Tomcatにはどのコアコンポーネントがあり、コンポーネント間の関係がわかります。そして、TomcatがHTTPリクエストをどのように処理するか。以下に、図から、さまざまなコンポーネントの階層的な関係を表示できます。 4.1 Tomcatの全体的なアーキテクチャには、2つのコアコンポーネントコネクタとコンテナが含まれています。コネクタは外部通信を担当し、コンテナは内部処理を担当します。コネクタは、 Tomcatの全体的なアーキテクチャを学ぶことで、複雑なシステムを設計するための基本的なアイデアを得ることができます。まず、要件を分析し、高い凝集と低カップリングの原理に従ってサブモジュールを決定し、次にサブモジュール内の変更点と不変ポイントを見つけ、インターフェイスと抽象的なベースクラスを使用して不変ポイントをカプセル化し、抽象的なベースクラスのテンプレートメソッドを定義し、サブクラスを描写するための抽象的なポイントを実装します。 4.2コンビネーションモードは、オブザーバーモードを介してコンテナを管理し、スタートアップイベントをリリースするために使用され、デカップリングと開閉の原則を実現します。 Skeleton Abstractクラスとテンプレートメソッドは抽象的に変更され、変更されておらず、変更はサブクラスの実装に残されているため、コードの再利用と柔軟な拡張が実現します。責任チェーンを使用して、ロギングなどのリクエストを処理します。 4.3クラスローダーTomcatのカスタム 5。実際のシナリオアプリケーション[コネクタ]から[コンテナ]までのTomcatの全体的なアーキテクチャデザインの簡単な分析、いくつかのコンポーネントのデザインのアイデアとデザインパターンを詳しく説明しました。次のステップは、学んだことを適用し、エレガントなデザインから学び、実際の作業開発に適用する方法です。学習は模倣から始まります。 5.1責任チェーンモデル作業では、ユーザーがいくつかの情報を入力して、以下に示すように、会社の[産業および商業情報]、[司法情報]、[Zhonglog情報]などを確認することを選択できるという要件があり、各モジュールで再利用する必要があるモジュールの間にいくつかの公共事柄があります。 これはリクエストのようなもので、複数のモジュールで処理されます。したがって、各クエリモジュールを抽象化してバルブを処理し、この方法でこれらのバルブを保存することができます。新しいバルブを追加して、開閉の原理を実装し、さまざまな特定のバルブに一連のチェックコードを分離し、「不変の」機能を抽出するために抽象クラスを使用する必要があります。 特定の例コードは次のとおりです。 まず、処理バルブを要約すると、 /** *責任チェーンモード:各モジュールバルブを処理します*/ パブリックインターフェースValve { /** *電話* @param netCheckdto */ void invoke(netcheckdto netcheckdto); } 抽象的なベースクラスを定義し、コードを再利用します。 パブリックアブストラクトクラスAbstractCheckValveはバルブを実装しています{ Public final AnalysisReportLogdo getLateSthistoryData(netCheckdto netCheckdto、netCheckDatatypeenum checkdatatypeenum){ //履歴を取得し、コードロジックを省略します} //検証データソース構成を取得しますパブリックファイナルストリングgetModulesource(String QuerySource、Moduleenum moduleenum){ //コードロジックを省略} } [Baiduネガティブニュース]の対応する処理など、各モジュール処理のビジネスロジックを定義します 翻訳者 @サービス Public Class BaidunegativeValveはAbstractCheckValveを拡張します{ @オーバーライド public void invoke(netcheckdto netcheckdto){ } } 最後に、ユーザーがチェックするモジュールを管理し、リストから保存します。必要な検証モジュールをトリガーするために使用されます 翻訳者 @サービス パブリッククラスNetCheckservice { //すべてのバルブ@autowiredを注入します プライベートマップ<文字列、バルブ> valvemap; /** *確認リクエストを送信します* * @Param NetCheckdto */ @async( "asyncexecutor") public void sendcheckrequest(netcheckdto netcheckdto){ //顧客選択の処理リストを保存するために使用されるモジュールバルブ<valve> valves = new ArrayList <>(); CheckModuleConfigdto CheckModuleConfig = netCheckdto.getCheckModuleConfig(); //ユーザーが選択したモジュールをバルブチェーンに追加するif(checkmoduleconfig.getBaidunegative()){ valves.add(valvemap.get( "baidunegativevalve")); } //一部のコードは省略されています...... if(collectutils.isempty(valves)){ log.info( "ネットワークチェックモジュールは空で、チェックする必要があるタスクはありません"); 戻る; } // processing valves.foreach(valve-> valve.invoke(netCheckdto)); } } 5.2テンプレートメソッドモード要件は次のとおりです。財務報告分析は、顧客または会社名が入力した財務報告書Excelデータに基づいて実行できます。 リストされていないものの場合は、データが合法かどうかを解析します - >計算を実行します。 リストされている企業:名前が存在しない場合は、電子メールを送信して計算を中止します。 重要な「変更」と「解散」、
アルゴリズムプロセス全体は固定テンプレートですが、アルゴリズムの内部変更をさまざまなサブクラス実装に遅らせる必要があります。 public abstract class abstractanalysistemplate { /** *財務レポート分析テンプレートメソッドを送信し、スケルトンプロセスを定義します* @param ReportAnalysisRequest * @戻る */ 公開最終財務分析は、ドプロセス(FinancialReportAnalysisRequest ReportAnalysisRequest){ FinancialAnalysisResultdto Analysisdto = new FinancialAnalysisResultdto(); //要約方法:検証boolean prepareValidate = prepareValidate(ReportAnalysisResRequest、AnalysisDTO)の法的検証を提出します。 log.info( "preparevalidate verification result = {}"、preparevalidate); if(!prepareValidate){ //要約方法:BuildEmailData(AnalysisDTO); log.info( "Build email Information、data = {}"、json.tojsonstring(analysisdto)); AnalysisDtoを返します。 } String Reportno = Financial_Report_no_prefix + reportanalysisrequest.getUserid() + serialnumgenerator.getfixlenthserialnumber(); //分析ログinitfinancialAnalysislog(reportAnalysisRequest、ReportNO); //分析レコードinitAnalysisResisReport(ReportAnalysisRequest、ReportNo); 試す { //要約方法:財務報告データをプルし、FinancialDatadto FinancialData = PullfinancialData(ReportAnalysisRequest)を実装します。 log.info( "財務報告データを入れて完了し、計算を実行する準備をします"); // Indicatorの計算FinancialCalccontext.calc(ReportAnalysisRequest、FinancialData、ReportNO); //分析ログをSuccessCalc(ReportNo)に設定します。 } キャッチ (例外 e) { log.error( "例外は財務報告書計算サブタスク"、eで発生しました); //分析ログFailCalc(ReportNo)の設定に失敗しました。 eを投げる; } AnalysisDtoを返します。 } } 最後に、テンプレートを継承し、抽象的なメソッドを実装するために、2つの新しいサブクラスが作成されます。これにより、リストされているものと非リストの両方の処理ロジックが切り離され、同時にコードが再利用されます。 5.3戦略モードこの要件は、[取引時間、収入、支出、取引バランス、支払人名、支払者名、支払人名]などのフィールドを識別できるユニバーサルエクセルインターフェイスを作成する必要があります。次に、必要な各フィールドが配置されているExcelヘッダーの添え字を解析します。しかし、流れる水には多くの状況があります: 1. 1つは、すべての標準フィールドを含めることです。 2。収入と支出の指標は同じ列であり、収入と支出は肯定的および否定的に区別されます。 3。収入と支出は同じ列であり、それを区別するためのトランザクションタイプのフィールドがあります。 4。特別銀行の特別な扱い。 つまり、対応するサブスクリプトに基づいて、対応する処理ロジックアルゴリズム 現時点では、ポリシーモードを使用し、異なるプロセッサを使用して異なるテンプレートのフローを処理し、テンプレートに応じて対応するポリシーアルゴリズムをプロセスできます。将来別のタイプを追加したとしても、新しいプロセッサを追加するだけで、これは高くなく、結合が低く、拡張できます。 プロセッサインターフェイスを定義し、さまざまなプロセッサが処理ロジックを実装します。すべてのプロセッサを パブリックインターフェイスデータプロセッサ{ /** *プロセスフローデータ* @param bankflowtemplatedoフローフローデータ* @param row * @戻る */ banktransactionflowdo doprocess(bankflowtemplatedo bankflowtemplatedo、list <string> row); /** *このテンプレートの処理がサポートされているかどうかにかかわらず、さまざまな種類のフロー戦略がテンプレートデータに基づいて解像度がサポートされているかどうかを決定します* @return */ ブールイスポート(bankflowtemplatedo bankflowtemplatedo); } // Processor Context @Service 翻訳者 パブリッククラスのbankflowdatacontext { //すべてのプロセッサをMAP @Autowiredに注入します プライベートリスト<DataProcessor>プロセッサ。 //対応するプロセッサを見つけてフローを処理しますpublic void process(){ DataProcessor Processor = getProcessor(bankflowtemplatedo); for(dataprocessorプロセッサ:プロセッサ){ if(processor.issupport(bankflowtemplatedo)){ // rowはフローデータプロセッサの行です。Doprocess(bankflowtemplatedo、row); 壊す; } } } } デフォルトのプロセッサを定義し、新しい /** *デフォルトプロセッサ:標準フローテンプレート* */ @Component( "DefaultDataProcessor") 翻訳者 パブリッククラスdefaultDataprocessorはデータプロセッサを実装しています{ @オーバーライド Public BankTransactionFlowdoドプロセス(BankFlowTemplatedO BankFlowTemplatedo){ //処理ロジックの詳細を省略しますbanktransactionflowdoを返します。 } @オーバーライド パブリックストリング戦略(bankflowtemplatedo bankflowtemplatedo){ //省略して、ストリームがサポートされているかどうかを決定します。 ISDEFAULTを返します。 } } ポリシーモデルを通じて、さまざまな処理ロジックをさまざまな処理クラスに割り当てます。これは完全に分離され、拡張が促進されます。 インラインTomcatを使用したデバッグソースコード:github:https://github.com/uniquedong/tomcat-embedded 上記は、Tomcat Architecture DesignのTomcatアーキテクチャの原則を分析する詳細な内容です。 以下もご興味があるかもしれません:
|
<<: ウェブサイトのテキストはまだデザインする必要がありますか?
>>: Vue プロジェクトで SVG コンポーネントをパッケージ化して構成する手順
この記事では、Nodejs 開発プロセスで遭遇する配列の特性によって発生する問題と解決策、および配列...
この記事では、Web ページ レイアウト デザインのいくつかの簡単な原則をまとめ、Web ページ デ...
この記事では、WindowsでのMySQL 8.0.12のインストール手順と使用方法のチュートリアル...
この記事の例では、商品検索機能を実現するためのJavaScriptの具体的なコードを参考までに共有し...
質問アップロードするファイルのタイプを accept に追加することは、「表面的な」役割しか果たしま...
コンテナを通じてローカル パブリック IP アドレスを取得します。ローカル IP アドレスを使用して...
例:ヒント:このコンポーネントはvue-cropperの二次パッケージに基づいていますプラグインをイ...
Navicat を使用して IP 経由で直接接続すると、次のようなさまざまなエラーが報告されます: ...
ホーム ページに戻るための支払いカウントダウン ケースの概要: シンプルな js 構文、getEle...
初めて docker に触れたときは本当に戸惑いました。初心者向けのチュートリアルを長い間読みました...
この記事では、一般的な MySQL 最適化方法をいくつかまとめて簡単に紹介します。これは、フルタイム...
このコレクションには、あなたのデザインアイデアにインスピレーションを与える、輝いて光沢のある、優れた...
Web ページのパフォーマンスを向上させるにはどうすればよいでしょうか?ほとんどの開発者は、Java...
Nginx は、リバース プロキシ機能を使用して負荷分散を実装できるほか、フォワード プロキシ機能を...
この記事では、アコーディオン効果を実現するためのJavaScriptの具体的なコードを参考までに紹介...