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 に中止関数を追加する方法

推薦する

ウェブページのメモリ使用量とCPU使用量を削減する方法

一部の Web ページは大きく見えなくても開くのに非常に時間がかかる場合があります。一方、他の We...

Vue で ToDo アプリケーションを実装する例

背景まず最初に、私はフロントエンド開発の専門家ではないことを述べておきたいと思います。私の以前のコン...

シンプルなカルーセル効果を実現するネイティブ js

この記事では、シンプルなカルーセル効果を実現するためのjsの具体的なコードを参考までに紹介します。具...

Linux /etc/network/interfaces 設定インターフェース方法

Linux の /etc/network/interfaces ファイルは、ネットワーク インターフ...

Vue プロジェクトで TypeScript クラスを適用する方法

目次1. はじめに2. 使用1. @コンポーネント2. 計算、データ、方法3. @props 4. ...

Linux での MySQL 8.0.25 のインストールと設定のチュートリアル

LinuxにMySQL 8.0.25をインストールするための最新のチュートリアルを参考にしてください...

MySql のクラッシュとサービスの起動失敗の解決策

私は長い間PHPに触れてきましたが、インストール環境は非常に不慣れです。多くの問題に遭遇しました。B...

EasyUEFI を使用して Windows 10 で USB ドライブなしで Ubuntu 18 をインストールする

1. BIOSを確認するまず、コンピュータの起動モードを確認します。win+R と入力し、msinf...

CSS で text-align と margin: 0 auto を使用して中央に配置する例コード

CSSでtext-align、margin: 0 autoを使用して中央揃えにするtext-alig...

WindowsでのNginxの起動や停止などの基本操作コマンドの詳しい説明

Windows で Nginx を使用するには、Nginx サービスの起動、停止、Nginx のリロ...

JavaScriptを使って動的にテーブルを生成するケースの詳しい説明

目次序文実装のアイデア実装コード成果を達成する序文これは、テーブルを動的に追加する例です。[追加] ...

HTMLヘッダータグの使用に関する詳細な説明

HTMLはヘッドとボディの2つの部分で構成されています** ヘッド内のタグはヘッドタグです** タイ...

VueはTodoListの例をカプセル化し、ブラウザのローカルキャッシュのアプリケーションを実装します。

この記事では主に、Vue で TodoList をカプセル化するケースと、ブラウザのローカル キャッ...

Mysqlトランザクション処理の詳細な説明

1. MySQLのトランザクションの概念MySQL トランザクションは主に、操作量が多く複雑度の高い...

Linux でのマルチスレッドにおけるフォークの紹介

目次質問:ケース(1)子スレッドを作成する前にフォークするケース(2)子スレッドを作成した後にフォー...