Tomcatアーキテクチャの原則をアーキテクチャ設計に分析する

Tomcatアーキテクチャの原則をアーキテクチャ設計に分析する

1. 学習目標

1.1. Tomcatアーキテクチャの設計と原則をマスターして社内スキルを向上させる

マクロビュー

Tomcat は、「 Httpサーバー + Servletコンテナー」として、アプリケーション層プロトコルとネットワーク通信の詳細を隠蔽し、標準のRequestおよびResponseオブジェクトを提供します。特定のビジネス ロジックはバリエーション ポイントとして使用され、実装はユーザーに任されます。 SpringMVCなどのフレームワークを使用しますが、 TCP接続、 Httpプロトコルのデータ処理と応答を考慮する必要はありません。 Tomcat がこれらすべてを実行してくれるので、私たちは各リクエストの特定のビジネス ロジックにのみ集中する必要があります。

顕微鏡写真

Tomcat 、内部的に変更点と変更されない点を分離し、コンポーネント化された設計を使用して、「ロシア人形スタイル」の高度なカスタマイズ (組み合わせパターン) を実現します。各コンポーネントのライフサイクル管理にはいくつかの共通機能があり、それらをインターフェイスと抽象クラスに抽出して、特定のサブクラスに変更点を実装できるようにします。これがテンプレート メソッド設計パターンです。

現在普及しているマイクロサービスもこの考え方に従っており、モノリシック アプリケーションを機能に応じて「マイクロサービス」に分割します。分割プロセス中に共通点が抽出され、これらの共通点がコアとなる基本サービスまたは汎用ライブラリになります。 「中間プラットフォーム」の考え方についても同様です。

デザイン パターンは、変更をカプセル化するための強力なツールとなることがよくあります。デザイン パターンを適切に使用することで、コードとシステム設計をエレガントですっきりしたものにすることができます。

これは、優れたオープンソースソフトウェアを学ぶことで得られる「内なる力」であり、決して古くなることのない、そこに含まれる設計思想や哲学こそが根本的な道なのです。彼らの設計経験から学び、設計パターンを適切に使用して変更と定数をカプセル化し、ソース コードからの経験を活用して独自のシステム設計能力を向上させます。

1.2. リクエストがSpringにどのように接続されるかについてのマクロの理解

仕事の過程で、私たちはすでに Java 構文に精通しており、いくつかのデザイン パターンを「記憶」し、多くの Web フレームワークを使用してきましたが、実際のプロジェクトでそれらを使用する機会はほとんどありません。システムを独自に設計することは、ニーズに応じて一度に 1 つのサービスを実装するだけのように思えます。私の頭の中には、Java Web 開発の全体像があまりないようです。たとえば、ブラウザのリクエストが Spring のコードとどのように関連しているかがわかりません。

このボトルネックを打破するために、大企業の肩に乗って優れたオープンソース システムを学び、大企業がこれらの問題に対してどのように考えているかを見てみてはいかがでしょうか。

Tomcat の原理を勉強した結果、 Servlet技術が Web 開発の起源であることが分かりました。ほぼすべての Java Web フレームワーク (Spring など) は、 Servletカプセル化に基づいています。Spring アプリケーション自体はServlet ( DispatchSevlet ) であり、Tomcat や Jetty などの Web コンテナーはServlet読み込みと実行を担当します。図に示すように:

1.3. システム設計能力の向上

Tomcat を学習しているときに、Java マルチスレッド並行プログラミング、ソケット ネットワーク プログラミング、リフレクションなどの高度な Java テクノロジを多数使用していることにも気付きました。以前は、これらのテクノロジーについて知っていて、面接のためにいくつかの質問を暗記しただけでした。しかし、「知っている」ことと「使える」ことの間にはギャップがあるといつも感じています。Tomcat のソース コードを研究することで、これらのテクノロジをどのようなシナリオで使用するかを学びました。

また、インターフェース指向プログラミング、コンポーネントベースの組み合わせモード、スケルトン抽象クラス、ワンクリック開始と停止、オブジェクトプールテクノロジー、テンプレートメソッド、オブザーバーモード、責任連鎖モードなどのさまざまな設計パターンなどのシステム設計機能もあります。その後、私はそれらを真似して、これらの設計アイデアを実際の作業に適用し始めました。

2. 全体的なアーキテクチャ設計

今日は、Tomcat の設計思想を段階的に分析します。一方では、Tomcat の全体的なアーキテクチャを学び、マクロの観点から複雑なシステムを設計する方法、トップレベル モジュールを設計する方法、モジュール間の関係を学ぶことができます。他方では、Tomcat の動作原理を詳細に研究するための基礎も築かれます。

Tomcat の起動プロセス:

startup.sh -> catalina.sh 開始 -> java -jar org.apache.catalina.startup.Bootstrap.main()

Tomcat は次の 2 つのコア機能を実装します。

  • Socket接続を処理し、ネットワーク バイト ストリームをRequestオブジェクトとResponseオブジェクトに変換する役割を担います。
  • Servletをロードして管理し、特定のRequestリクエストを処理します。

したがって、Tomcat はコネクタとコンテナという 2 つのコア コンポーネントで設計されています。コネクタは外部通信を担当し、コンテナは内部処理を担当します。

複数のI/Oモデルとアプリケーション層プロトコルをサポートするために、部屋に複数のドアがあるのと同じように、 Tomcatコンテナーは複数のコネクタに接続される場合があります。

  • サーバーは Tomcat インスタンスに対応します。
  • デフォルトでは、サービスは 1 つだけ、つまり 1 つの Tomcat インスタンスに対して 1 つのサービスだけがあります。
  • コネクタ: サービスには複数のコネクタがあり、異なる接続プロトコルを受け入れることができます。
  • コンテナー: 複数のコネクタが 1 つのコンテナーに対応し、最上位のコンテナーは実際にはエンジンです。

各コンポーネントには対応するライフサイクルがあり、起動する必要があり、その内部サブコンポーネントも起動する必要があります。たとえば、Tomcat インスタンスにはサービスが含まれ、サービスには複数のコネクタとコンテナが含まれます。コンテナには複数のホストが含まれ、ホスト内には複数のコンテキスト コンテナが存在する可能性があり、コンテキストには複数のサーブレットが含まれる可能性もあります。そのため、Tomcat は複合モードを使用して各コンポーネントを管理し、各コンポーネントを単一のグループとして扱います。全体的に、各コンポーネントのデザインは「ロシア人形」のようです。

2.1 コネクタ

コネクタについて説明する前に、まずTomcatでサポートされているさまざまなI/Oモデルとアプリケーション層プロトコルの基礎について説明します。

TomcatでサポートされているI/Oモデルは次のとおりです。

  • NIO : Java NIOクラス ライブラリを使用して実装された非ブロッキングI/O
  • NIO2 : JDK 7最新のNIO2クラス ライブラリを使用して実装された非同期I/O
  • APR : Apache Portable Runtime を使用して実装され、 C/C++で記述されたネイティブ ライブラリです。

Tomcat でサポートされているアプリケーション層プロトコルは次のとおりです。

  • HTTP/1.1 : これはほとんどの Web アプリケーションで使用されるアクセス プロトコルです。
  • AJP : Web サーバー (Apache など) との統合に使用されます。
  • HTTP/2 : HTTP 2.0 は Web パフォーマンスを大幅に向上させます。

したがって、1 つのコンテナーが複数のコネクタとドッキングする可能性があります。コネクタは、ネットワーク プロトコルとI/Oモデルの違いからServletコンテナを保護します。Http かAJPかに関係なく、コンテナで取得されるのHttp標準のServletRequestオブジェクトです。

改良されたコネクタの機能要件は次のとおりです。

  • リスニングネットワークポート。
  • ネットワーク接続要求を承認します。
  • 要求ネットワーク バイト ストリームを読み取ります。
  • 特定のアプリケーション層プロトコル ( HTTP/AJP ) に従ってバイト ストリームを解析し、統合されたTomcat Requestオブジェクトを生成します。
  • Tomcat Requestオブジェクトを標準のServletRequestに変換します。
  • Servletコンテナを呼び出して、 ServletResponseを取得します。
  • ServletResponseTomcat Responseオブジェクトに変換します。
  • Tomcat Responseネットワーク バイト ストリームに変換します。応答バイト ストリームをブラウザーに書き戻します。

要件が明確にリストされた後、次に検討する必要があるのは、コネクタにどのようなサブモジュールが必要かということです。優れたモジュール設計では、高い凝集性と低い結合性を考慮する必要があります。

  • 凝集度が高いということは、関連性の高い機能が分散されずに、可能な限り集中されることを意味します。
  • 結合度が低いということは、関連する 2 つのモジュールの依存関係と依存関係の度合いを可能な限り減らし、2 つのモジュール間の強い依存関係を回避することを意味します。

コネクタには、次の 3 つの非常にまとまりのある機能を実行する必要があることがわかりました。

  • ネットワーク通信。
  • アプリケーション層プロトコル分析。
  • Tomcat Request/ResponseServletRequest/ServletResponse間の変換。

そのため、Tomcat の設計者は、これらの 3 つの機能を実装するために、 EndPoint、Processor 和Adapterという 3 つのコンポーネントを設計しました。

ネットワーク通信の I/O モデルは変化しており、アプリケーション層プロトコルも変化していますが、全体的な処理ロジックは変更されていません。 EndPointProcessorにバイト ストリームを提供する役割を担い、 ProcessorAdapterTomcat Requestオブジェクトを提供する役割を担い、 AdapterコンテナーにServletRequestオブジェクトを提供する役割を担います。

2.2 カプセル化の変更と不変性

そのため、Tomcat はこれらの安定した部分をカプセル化するために一連の抽象基本クラスを設計しました。抽象基本クラスAbstractProtocol ProtocolHandlerインターフェイスを実装します。各アプリケーション層プロトコルには、 AbstractAjpProtocolAbstractHttp11Protocolなどの独自の抽象基本クラスがあり、特定のプロトコルの実装クラスはプロトコル層の抽象基本クラスを拡張します。

これはテンプレート メソッド デザイン パターンの応用です。

要約すると、コネクタの 3 つのコア コンポーネントであるEndpointProcessor 、およびAdapter 、それぞれ 3 つの機能を実行します。 EndpointProcessorProtocolHandlerコンポーネントに抽象化されています。それらの関係を次の図に示します。

ProtocolHandler コンポーネント:

主にネットワーク接続とアプリケーション層プロトコルを処理します。エンドポイントとプロセッサという 2 つの重要なコンポーネントが含まれています。この 2 つのコンポーネントが組み合わさって ProtocoHandler が形成されます。その動作原理を詳しく紹介します。

終点:

EndPointは通信エンドポイント、つまり通信監視用のインターフェイスです。これは、特定のソケット受信および送信プロセッサであり、トランスポート層の抽象化です。したがって、 EndPoint TCP/IPプロトコル データの読み取りと書き込みを実装するために使用され、本質的にはオペレーティング システムのソケット インターフェイスを呼び出します。

EndPoint NioEndpointインターフェースであり、対応する抽象実装クラスはAbstractEndpointです。NioEndpoint やNio2EndpointなどのAbstractEndpointの具体的なサブクラスには、 AcceptorSocketProcessor 2 つの重要なサブコンポーネントがあります。

アクセプターはソケット接続要求を監視するために使用されます。 SocketProcessorAcceptor受信したSocket要求を処理するために使用されます。 Runnableインターフェイスを実装し、 Runメソッドでアプリケーション層プロトコル処理コンポーネントProcessorを呼び出して処理します。処理能力を向上させるために、 SocketProcessor実行のためにスレッド プールに送信されます。

Java マルチプレクサの使用は、次の 2 つのステップで済むことがわかっています。

  • Seletor を作成し、それに関係するさまざまなイベントを登録してから、select メソッドを呼び出して、関係するイベントが発生するのを待ちます。
  • データが読み取り可能になるなど、何か興味深いことが起こると、チャネルからデータを読み取るための新しいスレッドが作成されます。

Tomcat では、 NioEndpointAbstractEndpointの具体的な実装です。 そこには多くのコンポーネントがありますが、処理ロジックは最初の 2 つのステップのままです。 LimitLatchAcceptorPollerSocketProcessorExecutor 5 つのコンポーネントが含まれており、これらが連携して TCP/IP プロトコル全体の処理を実装します。

LimitLatch は、最大接続数を制御する接続コントローラです。NIO モードのデフォルト値は 10,000 です。このしきい値に達すると、接続要求は拒否されます。

Acceptor別のスレッドで実行されます。新しい接続を受信するために、無限ループでacceptメソッドを呼び出します。新しい接続要求が到着すると、 acceptメソッドはChannelオブジェクトを返し、それがChannelに渡されて処理されます。

Pollerの本質はSelectorであり、これも別のスレッドで実行されます。 Poller内部的にChannel配列を維持します。無限ループでChannelのデータ準備状態を継続的に検出します。Channel Channel読み取り可能になると、 SocketProcessorタスク オブジェクトを生成し、 Executorに送信して処理します。

SocketProcessor は Runnable インターフェイスを実装します。このインターフェイスでは、run メソッド内のgetHandler().process(socketWrapper, SocketEvent.CONNECT_FAIL);コードがハンドラーを取得して socketWrapper の処理を​​実行し、最後にソケットを通じて適切なアプリケーション層プロトコル プロセッサを取得します。つまり、Http11Processor コンポーネントを呼び出して要求を処理します。 Http11Processor は Channel のデータを読み取って ServletRequest オブジェクトを生成します。Http11Processor は Channel を直接読み取りません。これは、Tomcat が同期非ブロッキング I/O モデルと非同期 I/O モデルをサポートしているためです。Java API では、対応する Channel クラスも AsynchronousSocketChannel や SocketChannel のように異なります。これらの違いを Http11Processor から隠すために、Tomcat は SocketWrapper というラッパー クラスを設計しました。Http11Processor は、データの読み取りと書き込みを行うために SocketWrapper メソッドのみを呼び出します。

ExecutorSocketProcessorタスク クラスの実行を担当するスレッド プールです。SocketProcessor SocketProcessor runメソッドは、 Http11Processorを呼び出して、要求データを読み取って解析します。 Http11Processorアプリケーション層プロトコルのカプセル化であることがわかっています。コンテナを呼び出して応答を取得し、 Channelを通じて応答を書き込みます。

ワークフローは次のとおりです。

プロセッサ:

プロセッサは、HTTP プロトコルを実装するために使用されます。プロセッサは、エンドポイントからソケットを受信し、バイト ストリームを読み取って Tomcat のリクエストおよびレスポンス オブジェクトに解析し、アダプタを介して処理するためにコンテナに送信します。プロセッサは、アプリケーション層プロトコルの抽象化です。

図から、EndPoint がソケット接続を受け取った後、SocketProcessor タスクを生成し、それをスレッド プールに送信して処理することがわかります。SocketProcessor の Run メソッドは、HttpProcessor コンポーネントを呼び出して、アプリケーション層プロトコルを解析します。Processor は解析によって Request オブジェクトを生成した後、Adapter の Service メソッドを呼び出します。このメソッドは、次のコードを通じてコン​​テナーに要求を渡します。

// コンテナを呼び出す
コネクタ.getService().getContainer().getPipeline().getFirst().invoke(リクエスト、レスポンス);

アダプタコンポーネント:

プロトコルが異なるため、Tomcat はリクエスト情報を格納するための独自のRequestクラスを定義しており、これは実際にオブジェクト指向の考え方を反映しています。ただし、この Request は標準のServletRequestではないため、Tomcat を使用してコンテナ内で直接 Request をパラメータとして定義することはできません。

Tomcat 設計者の解決策は、アダプタ パターンの典型的なアプリケーションであるCoyoteAdapter導入することです。コネクタはCoyoteAdapterSeviceメソッドを呼び出し、 Tomcat Requestオブジェクトを渡します。CoyoteAdapter CoyoteAdapter Tomcat Request ServletRequestに変換し、コンテナのServiceメソッドを呼び出す役割を担います。

2.3 コンテナ

コネクタは外部通信を担当し、コンテナは内部処理を担当します。具体的には、コネクタはソケット通信とアプリケーション層プロトコルの分析を処理してServlet要求を取得します。一方、コンテナはServlet要求の処理を担当します。

コンテナ: 名前が示すように、ものを保持するために使用されるため、Tomcat コンテナはServletをロードするために使用されます。

Tomcat は、 EngineHostContextWrapper 4 つのコンテナーを設計しました。 Server Tomcat インスタンスを表します。

次の図に示すように、これら 4 つのコンテナーは並列関係ではなく、親子関係にあることに注意してください。

なぜこれほど多くのレベルのコンテナを設計する必要があるのか​​と疑問に思うかもしれません。複雑さが増すのではないですか?実際、この背後にある考慮事項は、Tomcat が階層化アーキテクチャを使用して、サーブレット コンテナーを非常に柔軟にすることです。ここでは、1つのホストに複数のコンテキストがあり、1つのコンテキストにも複数のサーブレットが含まれており、各コンポーネントには統一されたライフサイクル管理が必要であるため、結合モードではこれらのコンテナを設計します。

Wrapper Servletを表し、 Context Web アプリケーションを表します。Web アプリケーションには複数のServletを含めることができます。Host Host仮想ホストまたはサイトを表します。Tomcat は複数のサイト (Host) で構成できます。サイト (Host) は複数の Web アプリケーションをデプロイできます。Engine Engine複数のサイト (Host) を管理するために使用されるエンジンを表します。Service には 1 つのEngineしか含めることができません。

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 はこれらのコンテナを管理するために結合モードを使用します。具体的な実装方法は、すべてのコンテナ コンポーネントがContainerインターフェイスを実装することです。これにより、複合モードでは、ユーザーは単一のコンテナ オブジェクトと複合コンテナ オブジェクトを一貫して使用できます。ここで、単一のコンテナ オブジェクトは最下位のWrapperを参照し、複合コンテナ オブジェクトは上記のContextHost 、またはEngineを参照します。 Containerインターフェイスは次のように定義されます。

パブリックインターフェースコンテナはライフサイクルを拡張します {
    パブリック void setName(文字列名);
    パブリック コンテナ getParent();
    パブリック void setParent(コンテナー コンテナー);
    パブリック void addChild(コンテナの子);
    パブリック void removeChild(コンテナの子);
    パブリック コンテナ findChild(文字列名);
}

これまでに、 getParentSetParentaddChildremoveChildなどのメソッドを見てきましたが、これらは、前述した組み合わせパターンを検証するだけです。また、 ContainerインターフェイスはLifecycleを拡張していることもわかります。Tomcat は、 Lifecycleを通じてすべてのコンテナ コンポーネントのライフサイクルを統一的に管理します。すべてのコンテナは結合モードを通じて管理され、 Lifecycle各コンポーネントのライフサイクル管理を実装するために拡張されます。 Lifecycleには主にinit()、start()、stop() 和destroy()の各メソッドが含まれます。

2.4. サーブレットの検索を要求するプロセス

リクエストは、どのWrapper Servletで処理されるのでしょうか?答えは、Tomcat はこのタスクを実行するために Mapper コンポーネントを使用するということです。

Mapperコンポーネントの機能は、ユーザーが要求したURL Servletに見つけることです。その動作原理は次のとおりです。Mapper Mapperは、Web アプリケーションの構成情報 (実際には、 Hostコンテナーに設定されたドメイン名、 Contextコンテナー内のWebアプリケーション パス、 Wrapperコンテナー内のServletマッピング パスなど、コンテナー コンポーネントとアクセス パス間のマッピング関係) を保存します。これらの構成情報は、マルチレベルMapであると想像できます。

リクエストが届くと、 Mapperコンポーネントはリクエスト URL 内のドメイン名とパスを解析し、保存されているマップ内を検索することでServletを見つけることができます。リクエスト URL は最終的にWrapperコンテナー、つまりServletのみを検索することに注意してください。

ユーザーが図のhttp://user.shopping.com:8080/order/buyなどの URL にアクセスすると、Tomcat はどのようにしてこの URL をサーブレットに見つけるのでしょうか。

1. まず、プロトコルとポート番号に基づいてサービスとエンジンを決定します。 Tomcat のデフォルトの HTTP コネクタはポート 8080 をリッスンし、デフォルトの AJP コネクタはポート 8009 をリッスンします。上記の例の URL はポート 8080 にアクセスするため、リクエストは HTTP コネクタによって受信され、コネクタはサービス コンポーネントに属しているため、サービス コンポーネントが決定されます。また、複数のコネクタに加えて、サービス コンポーネントにはコンテナー コンポーネント、具体的にはエンジン コンテナーもあるため、サービスが決定されるとエンジンも決定されることもわかっています。

2. ドメイン名に基づいてホストを選択します。サービスとエンジンが決定された後、Mapper コンポーネントは URL 内のドメイン名を通じて対応する Host コンテナを検索します。たとえば、例の URL によってアクセスされるドメイン名はuser.shopping.comなので、Mapper は Host2 コンテナを見つけます。

3. URL パスに基づいてコンテキスト コンポーネントを見つけます。ホストが決定された後、Mapper は URL パスに従って対応する Web アプリケーションのパスを照合します。たとえば、この例では、アクセスされたパスは /order なので、コンテキスト コンテナ Context4 が見つかります。

4. URL パスに基づいてラッパー (サーブレット) を見つけます。コンテキストが決定された後、マッパーは web.xml で構成されたサーブレット マッピング パスに従って特定のラッパーとサーブレットを検索します。

コネクタ内のアダプタはコンテナのサービス メソッドを呼び出してサーブレットを実行します。リクエストを受信する最初のコンテナはエンジン コンテナです。エンジン コンテナはリクエストを処理した後、リクエストを子コンテナのホストに渡してさらに処理します。最後に、リクエストはラッパー コンテナに渡され、ラッパーは処理のために最終的なサーブレットを呼び出します。では、この呼び出しプロセスはどのように実装されるのでしょうか?答えは、パイプラインバルブパイプラインを使用することです。

Pipeline-Valveは責任連鎖モデルです。責任連鎖モデルとは、リクエスト処理のプロセスにおいて、リクエストを順番に処理するプロセッサが多数存在することを意味します。各プロセッサは、自身の対応する処理を担当します。処理後、次のプロセッサを呼び出して処理を続行します。Valve は処理ポイント (つまり、処理バルブ) を表すため、 invokeメソッドを使用してリクエストを処理します。

パブリックインターフェース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にはaddValveメソッドがあります。パイプラインにはValveリストが保持されます。 Valve Pipelineに挿入して、リクエストに対して特定の処理を実行できます。また、呼び出しチェーン全体のトリガーは Valve によって完了するため、Pipeline には呼び出しメソッドがないこともわかりました。Valve Valve自身の処理を完了すると、 getNext.invoke()を呼び出して次の Valve 呼び出しをトリガーします。

実際、各コンテナには Pipeline オブジェクトがあります。この Pipeline の最初のバルブがトリガーされる限り、このコンテナのPipeline内のすべてのバルブが呼び出されます。しかし、異なるコンテナのパイプラインはチェーン内でどのようにトリガーされるのでしょうか? たとえば、エンジン内のパイプラインは、下位レベルのコンテナ ホスト内のパイプラインを呼び出す必要があります。

これは、 PipelinegetBasicメソッドもあるためです。このBasicValveValveリンク リストの最後にあります。これはPipelineの必須のValveであり、下位コンテナーのパイプラインの最初のバルブを呼び出す役割を担います。

プロセス全体はコネクタ内のCoyoteAdapterによってトリガーされ、エンジンの最初のバルブを呼び出します。

@オーバーライド
パブリック void サービス (org.apache.coyote.Request req、org.apache.coyote.Response res) {
    // 他のコードは省略 // コンテナの呼び出し
    コネクタ.getService().getContainer().getPipeline().getFirst().invoke()
        リクエスト、レスポンス);
    ...
}

ラッパー コンテナの最後のバルブはフィルター チェーンを作成し、 doFilter()メソッドを呼び出します。このメソッドは最終的にServletserviceメソッドに転送されます。

以前、 Filterについて話しませんでしたか?似たような機能があるようですね。では、 ValveFilterの違いは何でしょうか?それらの違いは次のとおりです。

  • ValveTomcatのプライベート メカニズムであり、Tomcat のインフラストラクチャAPIと密接に結合されています。 Servlet APIパブリック標準であり、Jetty を含むすべての Web コンテナーはフィルター メカニズムをサポートしています。
  • もう 1 つの重要な違いは、 Valve Web コンテナ レベルで動作し、すべてのアプリケーションからのリクエストをインターセプトするのに対し、 Servlet Filterアプリケーション レベルで動作し、特定のWebアプリケーションからのすべてのリクエストのみをインターセプトできることです。 Webコンテナー全体のインターセプターになりたい場合は、 Valveを介して行う必要があります。

ライフサイクル

先ほど、 ContainerコンテナがLifecycleライフサイクルを継承することを説明しました。システムが外部にサービスを提供するには、これらのコンポーネントを作成、組み立て、起動する必要があります。また、サービスが停止すると、リソースを解放してこれらのコンポーネントを破棄する必要があるため、これは動的なプロセスです。つまり、Tomcat はこれらのコンポーネントのライフサイクルを動的に管理する必要があります。

コンポーネントの作成、初期化、開始、停止、破棄を統一的に管理するにはどうすればよいでしょうか?コードロジックを明確にするにはどうすればよいでしょうか?コンポーネントを簡単に追加または削除するにはどうすればよいですか?コンポーネントが漏れや重複なく開始および停止されることをどのように確認すればよいでしょうか?

ワンタッチスタートとストップ:LifeCycleインターフェース

設計とは、システムの変化する点と不変の点を見つけることです。ここで不変の点は、各コンポーネントが作成、初期化、および起動のプロセスを経る必要があり、これらの状態と状態変換は変更されないままであるということです。変更点は、各特定コンポーネントの初期化方法、つまり起動方法が異なることです。

そのため、Tomcat は不変ポイントをライフサイクルに関連する LifeCycle と呼ばれるインターフェースに抽象化します。 LifeCycle インターフェースは、 init()、start()、stop() 和destroy()のいくつかのメソッドを定義し、それぞれの特定のコンポーネント (つまり、コンテナー) がこれらのメソッドを実装します。

親コンポーネントのinit()メソッドで、子コンポーネントを作成し、子コンポーネントのinit()メソッドを呼び出す必要があります。同様に、子コンポーネントのstart()メソッドも親コンポーネントのstart()メソッド内で呼び出す必要があるため、呼び出し元は各コンポーネントのinit()メソッドとstart()メソッドを無差別に呼び出すことができます。これが複合モードの使用であり、最上位コンポーネント、つまり Server コンポーネントのinit()メソッドとstart()メソッドが呼び出される限り、Tomcat 全体が起動します。そのため、Tomcat はコンテナの管理に複合モードを採用しています。コンテナは LifeCycle インターフェースを継承し、各コンテナのライフサイクルを単一のオブジェクトのように 1 回のクリックで管理し、Tomcat 全体を起動することができます。

スケーラビリティ: ライフサイクル イベント

システムのスケーラビリティという別の問題について考えてみましょう。各コンポーネントのinit()start()メソッドの具体的な実装は複雑で変更可能です。たとえば、Host コンテナの起動メソッドでは、webapps ディレクトリ内の Web アプリケーションをスキャンし、対応する Context コンテナを作成する必要があります。将来的に新しいロジックを追加する必要がある場合、 start()メソッドを直接変更できますか?これはオープン・クローズ原則に違反することになりますが、この問題をどのように解決すればよいのでしょうか?オープンクローズ原則では、システムの機能を拡張するために、システム内の既存のクラスを直接変更することはできないが、新しいクラスを定義することはできると規定されています。

コンポーネントのinit()およびstart()呼び出しは、親コンポーネントの状態変化によってトリガーされます。上位コンポーネントの初期化は子コンポーネントの初期化をトリガーし、上位コンポーネントの起動は子コンポーネントの起動をトリガーします。したがって、コンポーネントのライフサイクルを状態として定義し、状態遷移をイベントと見なします。イベントにはリスナーがあり、その中に何らかのロジックを実装することができ、リスナーも簡単に追加したり削除したりできます。これは典型的なオブザーバー パターンです。

Lyfecycleインターフェースの定義は次のとおりです。

再利用性: 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 はスケーラビリティと再利用性を考慮し、オブジェクト指向の考え方と設計パターンを極限まで取り入れています。 Containaerインターフェイスは、コンテナの親子関係を維持します。 Lifecycle組み合わせパターンは、コンポーネントのライフサイクルの維持を実装します。ライフサイクル内の各コンポーネントには、変更点と不変点があり、テンプレート メソッド パターンが使用されます。 複合パターン、オブザーバーパターン、スケルトン抽象クラス、およびテンプレートメソッドがそれぞれ使用されました。

親子関係のある多くのエンティティを維持する必要がある場合は、複合パターンの使用を検討してください。

オブザーバーのパターンは「ハイエンド」に聞こえますが、実際には、イベントが発生したときに一連の更新操作を実行する必要があることを意味します。低カップリング、非侵入通知および更新メカニズムが実装されています。

Containerは、Lifecyce、StandardContext、およびStandardWrapperがすべてのコンテナの継承であるため、コンテナベースクラスを実装します。

3.なぜTomcatが親の委任メカニズムを破るのか

3.1

JVMクラスローダーは、親の委任メカニズムに基づいてクラスをロードします。つまり、親のローダーが空になると、 Bootstrapをロードできないかどうかを確認します。 JDKは、3つの重要な方法を定義する抽象クラスクラスClassLoaderを提供します。 loadClass(String name) 用于子類重寫打破雙親委派:loadClass(String name, boolean resolve)

パブリック 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つのクラスローダーがあり、クラスのローダーをカスタマイズすることもできます。

  • BootstrapClassLoaderは、 rt.jarresources.jarなど、 JVMが起動するときに必要なコアクラスをロードするために使用されるスタートアップクラスローダーです。
  • ExtClassLoader \jre\lib\extディレクトリにJARパッケージをロードするために使用される拡張クラスローダーです。
  • AppClassLoaderは、アプリケーションがclasspathでクラスをロードするためにクラスをロードするために使用されるシステムクラスローダーです。
  • カスタムパスの下でクラスをロードするために使用されるカスタムクラスローダー。

これらのクラス ローダーの動作原理は同じですが、違いはロード パスが異なること、つまりfindClassメソッドによって検索されるパスが異なることです。親委任メカニズムは、Java クラスが JVM 内で一意であることを保証するものです。Object Objectなど、JRE コア クラスと同じ名前のクラスを誤って記述した場合、親委任メカニズムにより、記述したObjectクラスではなく、 JRE内のObjectクラスがロードされることが保証されます。これは、 AppClassLoaderオブジェクトクラスをロードすると、 ExtClassLoaderに委任してロードされ、 ExtClassLoader BootstrapClassLoader BootstrapClassLoaderに委任し、既にObject Objectをロードしていることがわかります。せいぜいExtClassLoaderを入手できます。ここで注意してください。

3.2

Tomcatは基本的に、バックグラウンドスレッドを介して定期的なタスクを実行し、クラスファイルの変更を定期的に検出し、変更が見つかった場合はクラスをリロードします。 ContainerBackgroundProcessorの実装方法を見てみましょう。

保護されたクラスコンテナバックグラウンドプロセッサは実行可能{

    @オーバーライド
    パブリックボイド実行() {
        //ここで渡されたパラメーターは、「ホストクラス」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のホットロードは、主にコンテキストコンテナのリロードメソッドを呼び出すことにより、コンテキストコンテナに実装されます。マクロの観点から詳細を脇に置いて、主なタスクは次のとおりです。

  • コンテキストコンテナとそのすべての子供の容器を停止して破壊します。これは、ラッパーのサーブレットインスタンスも破壊されることを意味します。
  • コンテキストコンテナに関連付けられたリスナーとフィルターを停止して破壊します。
  • コンテキストの下でパイプラインとさまざまなバルブを停止して破壊します。
  • Contextのクラスローダーとクラスファイルのリソースをクラスローダーによってロードしたクラスファイルリソースを停止して破棄します。
  • コンテキストコンテナを開始します。その間、前の4つのステップで破壊されたリソースが再現されます。

このプロセスでは、クラスローダーが重要な役割を果たします。コンテキストコンテナは、クラスローダーが破壊されると、ロードされたすべてのクラスも破壊されます。起動プロセス中に、コンテキストコンテナは新しいクラスファイルをロードする新しいクラスローダーを作成します。

3.3

TomcatのClass Loader WebAppClassLoaderは、最初にクラスを見つけることができない場合、その目的を委任します。具体的な実装は、 ClassLoaderの2つの方法を書き換えることです: findClassloadClass

Classメソッドを見つけます

org.apache.catalina.loader.webappclassloaderbase#findclass;

理解と読書を容易にするために、私はいくつかの詳細を削除しました:

パブリック 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。それが見つかっていない場合AppClassLoader 、親のローダーを検索してください。

3.しかし、親のローダーはこのクラスを見つけず、 ClassNotFound例外をスローします。

LoadClassメソッド

TomcatクラスローダーのloadClassメソッドの実装を見てみましょう。

パブリック 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アプリケーションでカスタマイズされている場合、オブジェクトクラスが最初にロードされます。 ExtClassLoader BootstrapClassLoader 、TomcatのクラスローダーがWebアプリケーションの下にBootstrapClassLoaderクラスをロードしないようにするため、JREコアクラスのExtClassLoaderの問題が回避されます。

4. ExtClassLoaderローダーがロードに失敗した場合、つまり、 JREコアクラスにそのようなクラスはありません。次に、ローカルWebアプリケーションディレクトリに検索と読み込みです。

5.ローカルディレクトリにそのようなクラスがない場合、それはWebアプリケーション自体によって定義されるクラスではないことを意味します。システムクラスローダーによってロードされます。 Class.forNameのデフォルトローダーはシステムクラスローダーであるため、 Class.forNameコールを介してWebアプリケーションがシステムクラスローダーに引き渡されることに注意してください。

6.上記のすべての読み込みプロセスが失敗した場合は、 ClassNotFound例外をスローします。

3.4

TomcatはServletコンテナであり、 Servletクラスのロードを担当しており、 Servletが依存しているJARパッケージをロードする責任もあります。また、 Tomcat自体もJavaプログラムであるため、独自のクラスと依存関係のJARパッケージをロードする必要があります。まず、これらの質問について考えてみましょう。

1. Tomcatで2つのWebアプリケーションを実行すると、2つのWebアプリケーションに同じ名前のServletがありますが、機能は異なります。Tomcatは、同じ名前のこれら2つのServletクラスを同時にロードおよび管理する必要があるため、Webアプリケーション間のクラスを分離する必要があります。

2。両方Tomcat Webアプリケーションが、 Spring Spring Spring同じサードパーティのJARパッケージに依存している場合、2つのWebアプリケーションを共有できることJVM確認する必要があります。

3. JVMと同様に、Tomcat自体のクラスとWebアプリケーションのクラスを分離する必要があります。

1。WebAppClassLoader

Tomcatのソリューションは、クラスローダーWebAppClassLoaderをカスタマイズし、各Webアプリケーションのクラスローダーインスタンスを作成することです。コンテキストコンテナコンポーネントはWebアプリケーションに対応するため、各ContextコンテナがWebAppClassLoaderローダーインスタンスの作成と保守を担当することがわかっています。この背後にある原則は、異なるローダーインスタンスによってロードされたクラスが、同じクラス名を持っていても、異なるクラスと見なされることです。これは、Java仮想マシン内に孤立したJavaクラススペースを作成することに相当します。各Webアプリケーションには、それぞれのクラスローダーを介してWebアプリケーションが互いに分離されています。

2。SharedClassloader

重要な要件は、2つのWebアプリケーション間でライブラリクラスを共有する方法であり、同じクラスを繰り返しロードすることはできません。親代表団のメカニズムでは、各チャイルドローダーは親ローダーを介してクラスをロードできます。そのため、親ローダーのロードパスで共有する必要があるクラスを配置するだけでは十分ではありませんか?

したがって、Tomcatのデザイナーは、特にWebアプリケーション間で共有されるクラスをロードするために、 WebAppClassLoaderの親ローダーであるクラスローダーSharedClassLoaderを追加しました。 WebAppClassLoader自体SharedClassLoaderクラスをロードしない場合、Parent Loader SharedClassLoaderにこのクラスをロードするように指定されたクラスをロードし、 WebAppClassLoaderに戻します。

3。Catalinaclassloader

Tomcat自体のクラスをWebアプリケーションのクラスから分離する方法は?

共有するには、父と息子の関係を経て、隔離するには兄弟関係が必要です。同胞団は、2つのクラスローダーが並行していることを指し、これに基づいて同じ親のローダーCatalinaClassloader持っている可能性があります。

このデザインには問題があります。

古い方法は、 CommonClassLoaderを追加してCatalinaClassloaderSharedClassLoaderの親ローダーとして機能することです。 CommonClassLoaderがロードできるすべてのクラスは、 CatalinaClassLoaderおよびSharedClassLoaderで使用できます

4。全体的なアーキテクチャの設計分析と利益の要約

Tomcatの全体的なアーキテクチャの以前の学習を通じて、Tomcatにはどのコアコンポーネントがあり、コンポーネント間の関係がわかります。そして、TomcatがHTTPリクエストをどのように処理するか。以下に、図から、さまざまなコンポーネントの階層的な関係を表示できます。

4.1

Tomcatの全体的なアーキテクチャには、2つのコアコンポーネントコネクタとコンテナが含まれています。コネクタは外部通信を担当し、コンテナは内部処理を担当します。コネクタは、 ProtocolHandler ProtocolHandlerインターフェイスEndPoint使用しProccesor Socket通信EndPointI/Oモデルの違いProcessorカプセル化します。コネクタは、アダプターAdapterを介してコンテナを呼び出します。

Tomcatの全体的なアーキテクチャを学ぶことで、複雑なシステムを設計するための基本的なアイデアを得ることができます。まず、要件を分析し、高い凝集と低カップリングの原理に従ってサブモジュールを決定し、次にサブモジュール内の変更点と不変ポイントを見つけ、インターフェイスと抽象的なベースクラスを使用して不変ポイントをカプセル化し、抽象的なベースクラスのテンプレートメソッドを定義し、サブクラスを描写するための抽象的なポイントを実装します。

4.2

コンビネーションモードは、オブザーバーモードを介してコンテナを管理し、スタートアップイベントをリリースするために使用され、デカップリングと開閉の原則を実現します。 Skeleton Abstractクラスとテンプレートメソッドは抽象的に変更され、変更されておらず、変更はサブクラスの実装に残されているため、コードの再利用と柔軟な拡張が実現します。責任チェーンを使用して、ロギングなどのリクエストを処理します。

4.3クラスローダー

TomcatのカスタムWebAppClassLoaderは、Webアプリケーションを分離するために、親クラスのローダーを紹介しようとします。 WebアプリケーションがJREのコアクラスを上書きし、ExtClassloaderを使用してロードするのを防ぎます。これにより、親の委任が破損し、安全にロードできます。

5。実際のシナリオアプリケーション

[コネクタ]から[コンテナ]までのTomcatの全体的なアーキテクチャデザインの簡単な分析、いくつかのコンポーネントのデザインのアイデアとデザインパターンを詳しく説明しました。次のステップは、学んだことを適用し、エレガントなデザインから学び、実際の作業開発に適用する方法です。学習は模倣から始まります。

5.1責任チェーンモデル

作業では、ユーザーがいくつかの情報を入力して、以下に示すように、会社の[産業および商業情報]、[司法情報]、[Zhonglog情報]などを確認することを選択できるという要件があり、各モジュールで再利用する必要があるモジュールの間にいくつかの公共事柄があります。

これはリクエストのようなもので、複数のモジュールで処理されます。したがって、各クエリモジュールを抽象化してバルブを処理し、この方法でこれらのバルブを保存することができます。新しいバルブを追加して、開閉の原理を実装し、さまざまな特定のバルブに一連のチェックコードを分離し、「不変の」機能を抽出するために抽象クラスを使用する必要があります。

特定の例コードは次のとおりです。

まず、処理バルブを要約すると、 NetCheckDTOリクエスト情報です

/**
 *責任チェーンモード:各モジュールバルブを処理します*/
パブリックインターフェース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。特別銀行の特別な扱い。

つまり、対応するサブスクリプトに基づいて、対応する処理ロジックアルゴリズムif else見つける必要があります。最後に、「臭くて長く、維持が難しい」というコードの複雑さ。

現時点では、ポリシーモードを使用し、異なるプロセッサを使用して異なるテンプレートのフローを処理し、テンプレートに応じて対応するポリシーアルゴリズムをプロセスできます。将来別のタイプを追加したとしても、新しいプロセッサを追加するだけで、これは高くなく、結合が低く、拡張できます。

プロセッサインターフェイスを定義し、さまざまなプロセッサが処理ロジックを実装します。すべてのプロセッサをBankFlowDataHandlerdata_processor_mapに注入し、さまざまなシナリオに従って既存のプロセッサのプロセスフローを削除します。

パブリックインターフェイスデータプロセッサ{
    /**
     *プロセスフローデータ* @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);
             壊す;
           }
         }

    }


}

デフォルトのプロセッサを定義し、新しいDataProcessorを処理します。

/**
 *デフォルトプロセッサ:標準フローテンプレート*
 */
@Component( "DefaultDataProcessor")
翻訳者
パブリッククラスdefaultDataprocessorはデータプロセッサを実装しています{

    @オーバーライド
    Public BankTransactionFlowdoドプロセス(BankFlowTemplatedO BankFlowTemplatedo){
        //処理ロジックの詳細を省略しますbanktransactionflowdoを返します。
    }

    @オーバーライド
    パブリックストリング戦略(bankflowtemplatedo bankflowtemplatedo){
      //省略して、ストリームがサポートされているかどうかを決定します。

      ISDEFAULTを返します。
    }
}

ポリシーモデルを通じて、さまざまな処理ロジックをさまざまな処理クラスに割り当てます。これは完全に分離され、拡張が促進されます。

インラインTomcatを使用したデバッグソースコード:github:https://github.com/uniquedong/tomcat-embedded

上記は、Tomcat Architecture DesignのTomcatアーキテクチャの原則を分析する詳細な内容です。

以下もご興味があるかもしれません:
  • tomcat8 に基づく無効な文字エンコーディング フィルタの問題に対する解決策
  • Tomcat コアコンポーネントとアプリケーションアーキテクチャの詳細な説明
  • Tomcat が設定ファイルを外部に配置するためのソリューション
  • Webリクエストと処理のTomcatソースコード分析
  • Tomcat でよく使われるフィルターの詳細な説明

<<:  ウェブサイトのテキストはまだデザインする必要がありますか?

>>:  Vue プロジェクトで SVG コンポーネントをパッケージ化して構成する手順

推薦する

Nodejs 配列キューと forEach アプリケーションの詳細な説明

この記事では、Nodejs 開発プロセスで遭遇する配列の特性によって発生する問題と解決策、および配列...

ウェブページレイアウトデザインのシンプルな原則

この記事では、Web ページ レイアウト デザインのいくつかの簡単な原則をまとめ、Web ページ デ...

Windows での MySQL 8.0.12 のインストール手順と基本的な使用方法のチュートリアル

この記事では、WindowsでのMySQL 8.0.12のインストール手順と使用方法のチュートリアル...

商品クエリ機能を実現するJavaScript

この記事の例では、商品検索機能を実現するためのJavaScriptの具体的なコードを参考までに共有し...

Element-ui アップロードファイルのアップロード制限の解決策

質問アップロードするファイルのタイプを accept に追加することは、「表面的な」役割しか果たしま...

dockerコンテナは直接実行され、pingを介してパブリックIP操作を取得します。

コンテナを通じてローカル パブリック IP アドレスを取得します。ローカル IP アドレスを使用して...

Vue 画像切り抜きコンポーネントのサンプルコード

例:ヒント:このコンポーネントはvue-cropperの二次パッケージに基づいていますプラグインをイ...

Navicat 経由で MySQL にリモート接続する方法

Navicat を使用して IP 経由で直接接続すると、次のようなさまざまなエラーが報告されます: ...

支払いカウントダウンを実現し、ホームページに戻るためのjs

ホーム ページに戻るための支払いカウントダウン ケースの概要: シンプルな js 構文、getEle...

SSHを使用してDockerサーバーに接続する方法

初めて docker に触れたときは本当に戸惑いました。初心者向けのチュートリアルを長い間読みました...

MySQL パフォーマンスの包括的な最適化方法リファレンス、CPU、ファイルシステムの選択から mysql.cnf パラメータの最適化まで

この記事では、一般的な MySQL 最適化方法をいくつかまとめて簡単に紹介します。これは、フルタイム...

光沢のある輝くウェブサイトデザインの感動的なデザイン例

このコレクションには、あなたのデザインアイデアにインスピレーションを与える、輝いて光沢のある、優れた...

高性能な HTML アプリケーションを作成するためのヒント

Web ページのパフォーマンスを向上させるにはどうすればよいでしょうか?ほとんどの開発者は、Java...

nginx を使用したプロキシ サーバーの設定

Nginx は、リバース プロキシ機能を使用して負荷分散を実装できるほか、フォワード プロキシ機能を...

アコーディオン効果を実現するJavaScript

この記事では、アコーディオン効果を実現するためのJavaScriptの具体的なコードを参考までに紹介...