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

推薦する

Django が uwsgi+nginx プロキシで静的リソースにアクセスできない問題の解決方法

uwsgi+nginx プロキシ Django をデプロイする場合、uwsgi を使用したアクセスは...

mysql8.0.19 winx64バージョンのインストール問題を解決する

MySQL は、スウェーデンの会社 MySQL AB によって開発されたオープンソースの小規模なリレ...

MySQL の count()、group by、order by の詳細な説明

最近、IM を実行するときに、これらの 3 つのキーワードを同時に使用したときに問題が発生しました。...

MySQL 主キー ID を生成する方法 (自己増分、一意、不規則)

目次1. uuid関数を使用して、一意かつ不規則な主キーIDを生成します。 2. idの自動成長1....

ZFS とは何か? ZFS を使用する理由とその機能

ZFSの歴史Z ファイル システム (ZFS) は、2001 年に Matthew Ahrens と...

nginx ip ブラックリストの動的禁止の例

ウェブサイトが悪意を持ってリクエストされた場合、IP アドレスをブラックリストに登録することは重要な...

Linuxコマンド履歴の調整方法の詳細な説明

Linux システムの bash history コマンドは、以前に実行したコマンドを記憶し、再入力...

Docker のホスト間コンテナ通信オーバーレイ実装プロセスの詳細な説明

サーバーも 2 つあります。準備:コンテナのホスト名を設定する consul: kv タイプのストレ...

Windows 10 インストール vmware14 チュートリアル図

ソフトウェアのダウンロードソフトウェアのダウンロード リンク: https://pan.baidu....

Mysql 中国語ソートルールの説明

MySQL を使用する際、フィールドをソートしたりクエリしたりすることがよくあります。通常は、中国語...

Bootstrap 3.0 学習ノート グリッドシステムの原則

前の 2 つの記事の簡単な紹介を通じて、Bootstrap についての基礎的な理解が得られました。 ...

MySQLのよくある間違い

NULL 値によると、MySQL の NULL 値は単にデータがないことを意味します。NULL 値は...

Reactのコンポーネント共同利用実装

目次ネスティング親子コンポーネント通信ブラザーコンポーネント通信撤回するReact の Linked...

MySql で正規表現クエリを使用する方法

正規表現は、特定のパターンに一致するテキストを検索および置換するためによく使用されます。たとえば、テ...

Linux で Nginx 1.16.0 をインストールするための詳細なチュートリアル

最近 Linux をいじっていたので、nginx の新しいバージョンをインストールしたいと思いました...