Tomcat クラスローダーの実装方法とサンプルコード

Tomcat クラスローダーの実装方法とサンプルコード

Tomcat は内部的に複数の ClassLoader を定義し、アプリケーションとコンテナーが異なるリポジトリ内のクラスとリソースにアクセスできるようにしながら、アプリケーション間のクラスの分離を実現します。

1. Javaクラスローディングメカニズム

クラスのロードとは、コンパイルされたクラス ファイルを JVM メモリ (永続世代/メタスペース) にロードすることです。

クラス ローダーがクラスの分離を実現できる理由は、2 つのクラスが同じクラス ローダーによってロードされた場合にのみ等しくなり、そうでない場合は等しくなくなるためです。

ロード時に、JVM は親委任メカニズムを使用します。クラス ローダーがクラスをロードする場合、ロード順序は次のようになります。

まず、リクエストは親ローダーに委任されます。親ローダーがロードするクラスを見つけられない場合は、独自のリポジトリを探してロードを試みます。

このメカニズムの利点は、コア クラス ライブラリが上書きされないようにできることです。

サーブレット仕様によると、Web アプリケーション ローダーは少し異なります。Web アプリケーション ローダーは、上位に委任するのではなく、まず独自のリソース ライブラリを検索し、標準の委任メカニズムを破壊します。Tomcat の設計と実装を見てみましょう。

2. Tomcatクラスローダーの設計

Tomcat の全体的なクラス ローダー構造は次のとおりです。

JDK が提供するクラス ローダーは次のとおりです。

ブートストラップ - ブートストラップ クラス ローダーは JVM の一部で、<JAVA_HOME>/lib/ ディレクトリ内の特定のファイルをロードします。 拡張機能 - 拡張機能クラス ローダーは、<JAVA_HOME>/lib/ext/ ディレクトリ内のクラス ライブラリをロードします。 アプリケーション - アプリケーション クラス ローダーはシステム クラス ローダーとも呼ばれ、CLASSPATH で指定されたクラス ライブラリをロードします。

Tomcat が実装するクラス ローダーは次のとおりです。

Common - 親ローダーは AppClassLoader で、デフォルトで ${catalina.home}/lib/ ディレクトリのクラス ライブラリをロードします。 Catalina - 親ローダーは Common クラス ローダーで、catalina.properties 構成ファイルの server.loader によって構成されたリソースをロードします。これらのリソースは通常、Tomcat によって内部的に使用されるリソースです。 Shared - 親ローダーは Common クラス ローダーで、catalina.properties 構成ファイルの shared.loader によって構成されたリソースをロードします。これらのリソースは通常、すべての Web アプリケーションによって共有されるリソースです。 WebappX - 親ローダーは Shared ローダーで、/WEB-INF/classes のクラスと /WEB-INF/lib/ の jar パッケージをロードします。 JasperLoader - 親ローダーは Webapp ローダーで、作業ディレクトリの JSP アプリケーションをコンパイルすることによって生成されたクラス ファイルをロードします。

実装上、上図は継承関係ではなく、組み合わせによる親子関係となります。 Tomcat クラス ローダーのソース コード クラス ダイアグラム:

Common、Catalina、Shared はすべて StandardClassLoader のインスタンスです。デフォルトでは、それらは同じオブジェクトを参照します。 StandardClassLoader と URLClassLoader の間に違いはありません。WebappClassLoader は仕様に従って次の順序で検索して読み込みます。

JVM 内の Bootstrap リポジトリからロード アプリケーション ローダー パス (CLASSPATH) からロード Web プログラム内の /WEB-INF/classes ディレクトリからロード Web プログラム内の /WEB-INF/lib 内の jar ファイルからロード コンテナの共通ローダー リポジトリ (すべての Web プログラムで共有されるリソース) からロード

次に、ソースコードの実装を見てみましょう。

3. カスタムローダーの初期化

共通クラスローダーは、Bootstrap の initClassLoaders で初期化されます。ソースコードは次のとおりです。

プライベートvoid initClassLoaders() {
 試す {
 commonLoader = createClassLoader("common"、null);
 共通ローダーが null の場合
  // 設定ファイルはありません。このローダーがデフォルトになります。単一の環境にある可能性があります。
  共通ローダー = this.getClass().getClassLoader();
 }
 //リポジトリ パス構成ファイルのプレフィックスと親ローダーを指定して、ClassLoader インスタンスを作成します。catalinaLoader = createClassLoader("server", commonLoader);
 sharedLoader = createClassLoader("shared", commonLoader);
 } キャッチ (Throwable t) {
 log.error("クラスローダーの作成時に例外が発生しました", t);
 システム終了(1);
 }
}

3 つのクラス ローダーが作成されていることがわかります。CreateClassLoader は構成に従ってリソース ウェアハウス アドレスを取得し、最終的に StandardClassLoader インスタンスを返します。コア コードは次のとおりです。

プライベート ClassLoader createClassLoader(文字列名、ClassLoader 親)
 例外をスローします {

 文字列値 = CatalinaProperties.getProperty(name + ".loader");
 ((値 == null) || (値.equals("")) の場合
  return parent; // 設定されていない場合は、渡された親ローダーを返します ArrayList repositoryLocations = new ArrayList();
 ArrayList リポジトリタイプ = 新しい ArrayList();
 ...
 // リソースリポジトリパスを取得します。String[] locations = (String[]) repositoryLocations.toArray(new String[0]);
 Integer[] タイプ = (Integer[]) リポジトリタイプ.toArray(新しい Integer[0]);
 // StandardClassLoader オブジェクトを作成する ClassLoader classLoader = ClassLoaderFactory.createClassLoader
   (場所、タイプ、親);
 ...
 classLoader を返します。
}

クラス ローダーが初期化されると、Catalina オブジェクトが作成され、その load メソッドが最終的に呼び出されて server.xml が解析され、コンテナーの内部コンポーネントが初期化されます。では、Engine などのコンテナーは、この設定の親ローダーとどのように関係するのでしょうか?

Catalina オブジェクトには、すべてのコンポーネントの親ローダーである parentClassLoader メンバー変数があります。デフォルトは AppClassLoader です。このオブジェクトが作成されると、その setParentClassLoader メソッドが反映され、親ローダーが sharedLoader に設定されます。

Tomcat のトップレベル コンテナ エンジンが初期化されると、Digester には SetParentClassLoaderRule ルールがあり、Engine.setParentClassLoader メソッドを通じて Catalina の parentClassLoader を関連付けます。

4. 親の委任メカニズムを解除する方法

答えは、Thread.getContextClassLoader() を使用することです。これは、現在のスレッドのコンテキスト ローダーであり、コードの実行中に Thread.setContextClassLoader() を介して動的に設定できます。

デフォルトでは、スレッド コンテキスト ローダーは親スレッドから継承されます。つまり、すべてのスレッドのデフォルトのコンテキスト ローダーは最初に開始されたスレッド、つまりメイン スレッドと同じであり、そのコンテキスト ローダーは AppClassLoader です。

StandardContext が起動すると、Tomcat はまず WebappClassLoader を初期化し、それを現在のスレッドのコンテキスト ローダーとして設定します。最後に、それを Loader オブジェクトとしてカプセル化し、コンテナー間の親子関係を利用して Servlet クラスをロードするときに使用します。

5. Webアプリケーションのクラスローディング

Web アプリケーションのクラスのロードは、WebappClassLoader メソッド loadClass(String, boolean) によって完了します。コア コードは次のとおりです。

J2SEの上書き防止

パブリック同期クラス loadClass(String name, boolean resolve)
 ClassNotFoundException をスローします {
 ...
 クラス clazz = null;
 // (0) 内部キャッシュにロードされているかどうかを確認します clazz = findLoadedClass0(name);
 (clazz != null)の場合{
 (log.isDebugEnabled())の場合
  log.debug("キャッシュからクラスを返す");
 もし (解決) が、resolvClass(clazz) を解決した場合;
 戻る(clazz)
 }
 // (0.1) JVM キャッシュにすでにロードされているかどうかを確認します clazz = findLoadedClass(name);
 (clazz != null)の場合{
 (log.isDebugEnabled())の場合
  log.debug("キャッシュからクラスを返す");
 もし、(解決)resolveClass(clazz) が見つかったら;
 戻る(clazz)
 }
 // (0.2) J2SEクラスの上書きを防ぐためにシステムクラスローダーを使用する try {
 clazz = system.loadClass(名前);
 (clazz != null)の場合{
  もし、(解決)resolveClass(clazz) が見つかったら;
  戻る(clazz);
 }
 } catch (ClassNotFoundException e) { // 無視 }
 // (0.5) SecurityManagerを使用してこのクラスへのアクセス権があるかどうかを確認します。if (securityManager != null) {
 int i = name.lastIndexOf('.');
 もし (i >= 0) {
  試す {
  セキュリティマネージャのパッケージアクセスをチェックします(名前のサブ文字列(0,i));
  } キャッチ (SecurityException se) {
  文字列エラー = "セキュリティ違反、" + を使用しようとしました
   「制限クラス: 」+ 名前;
  log.info(エラー、se);
  新しい ClassNotFoundException(error, se) をスローします。
  }
 }
 }
 ブール型 delegateLoad = delegate || filter(name);
 // (1) 親クラスに委譲するかどうか。デフォルト値は false
 if (delegateLoad) {
  ...
 }
 // (2) 独自のリポジトリを見つけてロードしてみる try {
 clazz = findClass(名前);
 (clazz != null)の場合{
  (log.isDebugEnabled())の場合
  log.debug("ローカルリポジトリからクラスをロードしています");
  もし、(解決)resolveClass(clazz) が見つかったら;
  戻る(clazz);
 }
 } キャッチ (ClassNotFoundException e) {}
 // (3) この時点でも読み込みが失敗する場合は、読み込み要求を親ローダーに委任します。if (!delegateLoad) {
 (log.isDebugEnabled())の場合
  log.debug(" 最後に親クラスローダーに委任しています: " + parent);
 ClassLoader ローダー = 親;
 if (ローダー == null)
  ローダー = システム;
 試す {
  clazz = loader.loadClass(名前);
  (clazz != null)の場合{
  (log.isDebugEnabled())の場合
   log.debug("親からクラスをロードしています");
  もし (解決) が、resolvClass(clazz) を解決した場合;
  戻る(clazz)
  }
 } キャッチ (ClassNotFoundException e) {}
 }
 //最後に、読み込みが失敗し、例外がスローされます throw new ClassNotFoundException(name);
}

J2SE クラスが上書きされないようにするために、Tomcat 6 は AppClassLoader を使用します。rt.jar コア クラス ライブラリは Bootstrap Classloader によってロードされますが、このローダーは Java コードでは使用できません。上位バージョンでは、次の最適化が行われます。
クラスローダー j = String.class.getClassLoader();
j == nullの場合{
 j = getSystemClassLoader();
 (j.getParent() != null) の場合 {
 j = j.getParent();
 }
}
this.javaseClassLoader を j に設定します。

クラスをロードするときに、Tomcat 6 は AppClassLoader を使用します。rt.jar コア クラス ライブラリは Bootstrap Classloader によってロードされますが、このローダーは Java コードでは取得できません。上位バージョンでは、次の最適化が行われます。

クラスローダー j = String.class.getClassLoader();
j == nullの場合{
 j = getSystemClassLoader();
 (j.getParent() != null) の場合 {
 j = j.getParent();
 }
}
this.javaseClassLoader を j に設定します。

つまり、Bootstrap ローダーにできるだけ近いクラス ローダーを使用します。

6. まとめ

ほとんどの人が ClassNotFoundException 例外に遭遇したことがあると思います。この背後にはクラス ローダーがあります。ローディングの原則をある程度理解しておくと、問題のトラブルシューティングに役立ちます。

上記は、編集者が紹介したTomcatクラスローダーの実装方法とサンプルコードです。皆様のお役に立てれば幸いです。ご質問がある場合は、メッセージを残してください。編集者がすぐに返信します。また、123WORDPRESS.COM ウェブサイトをサポートしてくださっている皆様にも感謝申し上げます。
この記事が役に立ったと思われた方は、ぜひ転載していただき、出典を明記してください。ありがとうございます!

以下もご興味があるかもしれません:
  • Tomcat を起動したときに Eclipse が Web プロジェクトをロードできない問題を解決する
  • Tomcat での jar のロードに関する異常な問題の分析と解決
  • Tomcatのホットデプロイメントとホットロード方式の詳細な説明
  • Tomcat が CSS や JS などの静的リソース ファイルをロードできない場合の解決策
  • Java tomcat のクラスローダーとセキュリティメカニズムをご存知ですか?

<<:  便利でシンプルなMySQL関数10個

>>:  JS の Promise に中止関数を追加する方法

推薦する

モバイルレイアウト用の動的REMの実装

ダイナミックレム1. まず、現在の長さの単位を紹介しましょうpx em Mの幅 / 漢字の幅 1em...

JavaScript Canvas は動的なワイヤーフレーム効果を描画します

この記事では、JavaScript Canvasの動的なワイヤーフレーム効果を描画する具体的なコード...

WeChatアプレットがチャットルーム機能を実現

この記事では、参考のために、WeChatアプレットのチャットルームを実装するための具体的なコードを例...

Dockerイメージをプライベートリポジトリにアップロードする方法の例

イメージは、GitHub と同様に Docker パブリック リポジトリに直接簡単にプッシュできます...

MySQL 8.0.17 インストール グラフィック チュートリアル

この記事では、参考までにMySQL 8.0.17のインストールグラフィックチュートリアルを紹介します...

MySQL インデックスの詳細な説明

目次1. インデックスの基本1.1 はじめに1.2 インデックスの仕組み1.3 インデックスの種類1...

HTML テーブルのオーバーフローの解決方法

テーブルが広い場合は、あふれてしまう可能性があります。たとえば、左と右の 2 つの div がありま...

Centos7でmysql5.7.19のデータ保存場所を移動する方法

シナリオ: データ量が増加すると、MySQL が配置されているディスクがいっぱいになり、より大きなス...

mysql は sql ファイルを実行し、エラーを報告します エラー: 不明なストレージ エンジン 'InnoDB' ソリューション

問題を見つける最近、仕事で問題が発生しました。InnoDB タイプの SQL ファイルを実行すると、...

複数サーバーの負荷分散を実現するためのNginx構成

Nginx ロード バランシング サーバー: IP: 192.168.0.4 (Nginx-Serv...

間違った MySQL コマンドをキャンセルしたい場合はどうすればいいですか?

間違った mysql コマンドを入力したのでキャンセルしたいです。どうすればいいですか? ctrl ...

Docker プライベート ウェアハウスを構築する (自己署名方式)

作成したイメージを一元管理し、サービスの展開を容易にするために、プライベート Docker リポジト...

JSはクリックドロップ効果を実装します

jsはクリックとドロップの特殊効果を実現します。まずは効果画像を見てみましょうさっそく始めましょう。...

Facebook 出会い系サイトデザインのユーザー エクスペリエンス分析

<br />関連記事: Facebookの情報アーキテクチャの分析 元記事: http:...

VueはOpenLayersを使用してTiandi MapとAmapを読み込み

目次1. 世界地図1. VueにOpenLayersをインストールする2. アマップ1. 世界地図1...