Tomcat が Java の親委任メカニズムを破壊するのはなぜですか?

1. クラスロードメカニズムとは何ですか?

コードのコンパイルの結果は、ローカル マシン コードからバイトコードに変換されます。これは、ストレージ形式では小さな一歩ですが、プログラミング言語の開発では大きな一歩です。Java 仮想マシンは、クラスを記述するデータを Class ファイルからメモリにロードし、データの検証、変換、解析、および初期化を行って、最終的に仮想マシンで直接使用できる Java 型を形成します。仮想マシンのロードメカニズム。仮想マシン設計チームは、アプリケーションがすべてのクラスを取得する方法を決定できるように、Java 仮想マシンの外部のクラス読み込みフェーズで「クラスの完全修飾名を通じてこのクラスを記述するバイナリ バイト ストリームを取得する」アクションを実装しました。 . 必須クラス。このアクションを実装するコード モジュールは「クラス ローダー」と呼ばれます。

クラスとクラスローダーの関係

クラスローダーはクラスのロードアクションを実装するためにのみ使用されますが、Java プログラムにおけるその役割はクラスのロード段階に限定されるわけではありません。どのクラスでも、Java 仮想マシン内での一意性は、それをロードするクラス ローダーとクラス自体によって確立される必要があります。各クラス ローダーは、独立したクラス名前空間を持ちます。この文はより一般的に表現できます。2 つのクラスが「等しい」かどうかを比較します。只有在这两个类是由同一个类加载器加载的前提下才有意义そうでない場合は、2 つのクラスが同じクラス ファイルから取得され、同じ仮想マシンによってロードされた場合でも、それらをロードするクラス ローダーが異なる限り、比較します。の場合、2 つのクラスは必然的に等しくなくなります。

2. 保護者予約モデルとは何ですか

  • Java 仮想マシンの観点から見ると、異なるクラス ローダーは 2 つだけあります: 1 つはブートストラップ クラス ローダー (Bootstrap ClassLoader)で、C++ 言語 (HotSpot のみ) で実装され、仮想マシン自体の一部です。もう 1 つは他のすべてのクラス ローダーは、仮想マシンから独立して Java 言語によって実装され、すべて抽象クラスから継承しますjava.lang.ClassLoader

  • Java 開発者の観点から見ると、クラスのロードはさらに詳細に分けることができ、ほとんどの Java プログラマは、システムによって提供される次の 3 種類のクラス ローダーを使用します。

    • ブートストラップ クラスローダー: このクラス ローダーは、JAVA_HOME/lib ディレクトリ、または -Xbootclasspath パラメーターで指定されたパスに保存され、仮想マシンによって認識されます (rt.jar などのファイル名によってのみ、名前が付けられているクラス ライブラリ)一致しない場合は、lib ディレクトリに配置されてもオーバーロードされません)。
    • 拡張クラスローダー (拡張クラスローダー): このクラスローダーは sun.misc.Launcher$ExtClassLoader によって実装されます。これは、JAVA_HOME/lib/ext ディレクトリ、または java.ext.dirs システム変数で指定されたパスの混合を担当します。 すべてのクラス図書館。開発者は、拡張クラス ローダーを直接使用できます。
    • アプリケーション クラスローダー (アプリケーション クラスローダー): このクラス ローダーは sun.misc.Launcher$AppClassLoader によって実装されます。このクラスローダはClassLoaderのgetSystemClassLoaderメソッドの戻り値となるため、システムクラスローダにもなります。ユーザー クラス パス (ClassPath) で指定されたクラス ライブラリをロードします。開発者はこのクラス ローダーを直接使用できます。アプリケーションが独自のクラス ローダーを定義していない場合、通常、これがプログラム内のデフォルトのクラス ローダーになります。

これらのクラス ローダー間の関係は、一般的に次の図に示されています。

図中の各クラスローダ間の関係が、クラスローダの親委任モデル(Parents Dlegation Mode)となります。親委任モデルでは、最上位の起動クラス ローダーを除き、残りのクラス ローダーは独自の親クラス ローダーによってロードされる必要があります。ここで、クラス ローダー間の親子関係は通常、継承によって実装されませんが、どちらも構成関係を使用して、親ローダーのコードを再利用します。

クラスローダーの親委任モデルは JDK1.2 のときに導入され、その後のすべての Java プログラムで広く使用されていますが、これは必須の制約モデルではなく、Java 設計者が開発者のデバイス実装に推奨するクラスローディングです。親委任モデルの動作プロセスは次のとおりです。クラス ローダーがクラス ロード要求を受信した場合、最初に単独でクラスをロードしようとするのではなく、要求を親クラス ローダーに委任して完了します。同じことがクラス ローダーのすべてのレベルに当てはまります。そのため、すべてのロード要求は最終的に最上位の起動クラス ローダーに送信される必要があります。親ローダーが要求 (必要なクラス) を完了できないと報告した場合にのみ、サブローダーが試行します。それ自体をロードします。

どうしてそれをするの?

親委任モデルが使用されず、各クラス ローダーが自動的にロードされる場合、ユーザーが java.lang.Object というクラスを作成してプログラムの ClassPath に配置すると、システムには複数の異なる Objects クラスが存在することになります Java 型システムの動作は保証できません。アプリケーションもめちゃくちゃになります。

親がモデルを委任する場合、それはどのように実装されますか?

非常に単純です。すべてのコードは java.lang.ClassLoader のloadClass メソッド内にあり、コードは次のとおりです。

ロジックは明確で理解しやすいです。まず、ロードされているかどうかを確認し、ロードされていない場合は、親ローダーのloadClassメソッドを呼び出します。親ローダーが空の場合は、デフォルトで起動クラスローダーを親ローダーとして使用します。親クラスのロードに失敗した場合は、ClassNotFoundException 例外をスローした後、独自の findClass メソッドを呼び出してロードします。

3. 親の委任モデルを打破するにはどうすればよいですか?

親委任モデルは必須の制約モデルではなく、推奨されるクラス ローダーの実装であると述べました。Java 世界のほとんどのクラス ローダーはこのモデルに従いますが、例外もあり、これまでに、親委任モデルは 3 回大規模に「破壊」されました。
初回: 親委任モデルが登場する前 ----- JDK1.2 のリリース前。
2回目:モデル自体の欠陥が原因です。親委任モデルは、各クラス ローダーの基本クラスを統合するという問題を非常にうまく解決します (より基本的なクラスは、上位レベルのローダーによってロードされます)。基本クラスは、常にクラス ローダーであるため、「基本」と呼ばれます。ユーザー コードによって呼び出される API ですが、絶対的なものではありません。基本クラスがユーザー コードを呼び出した場合はどうなるでしょうか? 不可能ではありません。典型的な例は JNDI サービスです JNDI は現在 Java の標準サービスです そのコードは起動クラスローダー (JDK1.3 では rt.jar が組み込まれています) によってロードされますが、独立したベンダーによって呼び出されてデプロイされる必要がありますアプリケーションの ClassPath の下にある JNDI インターフェイス プロバイダー (SPI、サービス プロバイダー インターフェイス) のコードですが、起動クラス ローダーがこれらのコードを「認識」することは不可能です。これらのクラスは rt.jar には含まれていないため、起動クラス ローダーをロードする必要があります。どうやってするの?

この問題を解決するために、Java 設計チームは、あまり洗練されていない設計であるThread Context ClassLoader (Thread Context ClassLoader)を導入する必要がありました。このクラス ローダーは、java.lang.Thread クラスの setContextClassLoader メソッドを通じて設定できます。スレッドの作成時に設定されていない場合は、親スレッドから継承されます。アプリケーションのグローバル スコープにあまり多くの設定がない場合、このクラス ローダーがデフォルトでアプリケーション クラス ローダーになります。

スレッド コンテキスト ローダーを使用すると、JNDI サービスはこのスレッド コンテキスト ローダーを使用して必要な SPI コードをロードします。つまり、親クラス ローダーが子クラス ローダーにクラス ロード アクションを完了するよう要求します。この動作は実際には、親委任モデルの階層構造を持たないクラスローダーの逆使用は、実際には親委任モデルの一般原則に違反しています。しかし、これは役に立たず、Java での SPI に関係するすべてのロード操作は基本的にこのメソッドを使用します。JNDI、JDBC、JCE、JAXB、JBI など。

3回目:ホットプラグ、ホットデプロイ、モジュール化を実現するには、再起動せずに機能を追加または削除することを意味し、クラスローダごとモジュールを置き換えるだけでコードのホットリプレースを実現します。

これで、Java のデフォルトのクラスローディングの原理を基本的に理解し、親の委任モデルも理解しました。ここまで言って、Tomcat のことをほとんど忘れていましたが、私たちの疑問は、なぜ Tomcat ローダーが親委任モデルに違反するのかということです。Tomcat クラスローダーについて話しましょう。

4. Tomcat のクラスローダーはどのように設計されていますか?

まず最初に質問しましょう。Tomcat はデフォルトのクラスロードメカニズムを使用できますか? 考えてみましょう: Tomcat は Web コンテナです。それではどのような問題が解決されますか:

  1. Web コンテナは 2 つのアプリケーションをデプロイする必要がある場合があります。異なるアプリケーションは、同じサードパーティ クラス ライブラリの異なるバージョンに依存する場合があります。同じサーバー上で同じクラス ライブラリのコピーを 1 つだけ必要とすることはできません。したがって、次のことを確認する必要があります。各アプリケーション クラス ライブラリはすべて独立しており、相互に分離されることが保証されています。
  2. 同じWebコンテナにデプロイされた同じクラスライブラリの同じバージョンを共有できます。そうしないと、サーバーに 10 個のアプリケーションがある場合、同じクラス ライブラリの 10 個のコピーを仮想マシンにロードする必要がありますが、これはナンセンスです。
  3. Web コンテナには独自の依存クラス ライブラリもあります。これをアプリケーション クラス ライブラリと混同しないでください。セキュリティ上の考慮事項に基づいて、コンテナのクラス ライブラリはプログラムのクラス ライブラリから分離する必要があります。
  4. Web コンテナは、jsp の変更をサポートする必要があります。仮想マシンで実行するには、最終的に jsp ファイルをクラス ファイルにコンパイルする必要があることはわかっていますが、プログラムの実行後に jsp を変更するのが一般的です。そうでない場合はどうしますか? したがって、Web コンテナは再起動せずに JSP の変更をサポートする必要があります。

もう一度質問を見てみましょう: Tomcat はデフォルトのクラスロードメカニズムを使用できますか?
答えはいいえだ。なぜ?最初の問題は、デフォルトのクラス ローダー メカニズムを使用する場合、同じクラス ライブラリの 2 つの異なるバージョンをロードできないことです。デフォルトのアキュムレータは、使用しているバージョンは関係ありません。完全修飾されたアキュムレータのみを考慮します。クラス名、コピーは 1 つだけ。2 番目の質問であるデフォルトのクラスローダーは、一意性を確保することが彼の責任であるため、実現可能です。3 番目の質問は最初の質問と同じです。もう一度 4 番目の質問を見てみましょう。jsp ファイルのホット変更をどのように実現できるかを考えます。jsp ファイルは実際にはクラス ファイルです。変更されてもクラス名が同じ場合、クラス ローダーは既存のファイルを直接フェッチします。はい、変更された JSP は再ロードされません。じゃあ何をすればいいの?jsp ファイルのクラスローダは直接アンインストールできるので、各 jsp ファイルに固有のクラスローダが対応していると考えていただければよいのですが、jsp ファイルが変更されると、jsp クラスローダも直接アンインストールされます。クラスローダーを再作成し、jsp ファイルを再ロードします。

Tomcat は独自のクラス読み込みメカニズムをどのように実装していますか?

では、Tomcat はどのようにしてそれを実現するのでしょうか? 素晴らしい Tomcat チームが設計しました。彼らのデザインを見てみましょう:

最初の 3 つのクラス ローダーは、デフォルトのものと一致していることがわかります。CommonClassLoader、CatalinaClassLoader、SharedClassLoader、および WebappClassLoader は、Tomcat/common/*によって定義されたクラス ローダーです。以下) および のJava クラス ライブラリです。通常、WebApp クラス ローダーと Jsp クラス ローダーには複数のインスタンスがあり、各 Web アプリケーションはWebApp クラス ローダーに対応し、各 JSP ファイルは Jsp クラス ローダーに対応します。/server/*/shared/*/WebApp/WEB-INF/*

  • commonLoader: Tomcat の最も基本的なクラス ローダー。ロード パス内のクラスには、Tomcat コンテナ自体と各 Web アプリからアクセスできます。
  • catalinaLoader: Tomcat コンテナーのプライベート クラス ローダー。読み込みパス内のクラスは Webapp には表示されません。
  • sharedLoader: 各 Web アプリによって共有されるクラス ローダー。読み込みパス内のクラスはすべての Web アプリに表示されますが、Tomcat コンテナーには表示されません。
  • WebappClassLoader: 各 Web アプリのプライベート クラス ローダー。読み込みパス内のクラスは現在の Web アプリにのみ表示されます。

それは、図の委任関係からわかります。

  • CommonClassLoader がロードできるクラスは Catalina ClassLoader と SharedClassLoader で使用できるため、パブリック クラス ライブラリの共有が実現しますが、CatalinaClassLoader と Shared ClassLoader 自体がロードできるクラスは互いに分離されています。
  • WebAppClassLoader は SharedClassLoader によってロードされたクラスを使用できますが、各 WebAppClassLoader インスタンスは互いに分離されています。
  • JasperLoader のロード範囲は、この JSP ファイルによってコンパイルされた .Class ファイルのみであり、その目的は破棄されることです。Web コンテナは、JSP ファイルが変更されたことを検出すると、JasperLoader の現在のインスタンスを置き換えて、Create a を渡します。 JSPファイルのHotSwap機能を実現するための新しいJspクラスローダ。

さて、これまでのところ、Tomcat がこのように設計されている理由と方法はすでにわかっていますが、では、Tomcat は Java が推奨する親委任モデルに違反しているのでしょうか? 答えは「違反している」です。前に述べたように、親の委任モデルでは、最上位の起動クラス ローダーを除き、他のすべてのクラス ローダーは親クラス ローダーによってロードされる必要があります。明らかに、tomcat はこの方法では実装されていません。分離を実現するために、tomcat はこの合意を遵守しません。各 webappClassLoader はクラス ファイルを独自のディレクトリにロードし、親クラス ローダーに渡しません。

私たちは質問を拡張しました: Tomcat の Common ClassLoader が WebApp ClassLoader にクラスをロードしたい場合はどうすればよいですか? 親委任モデルの破棄に関する前のコンテンツを読んだ後、それがよくわかりました。スレッド コンテキスト クラス ローダーを使用して実装できます。スレッド コンテキスト ローダーを使用すると、親クラス ローダーは子クラス ローダーにクラスのロードを完了するように要求できます。アクションです。素晴らしい。

おすすめ

転載: blog.csdn.net/qq_28165595/article/details/132256500