クラスローディングリファレンス
このドキュメントでは、Quarkus のクラスロードアーキテクチャについて説明します。このドキュメントは、Quarkus がどのように動作するかを正確に理解したいエクステンションの作者や上級ユーザーを対象としています。
Quarkusのクラスローディングアーキテクチャは、アプリケーションを実行するモードによって若干異なります。
fast-jar
パッケージタイプ (デフォルト) を使って本番アプリケーションを実行する場合、ほとんどすべての依存関係は、ビルド時にクラスをインデックスする io.quarkus.bootstrap.runner.RunnerClassLoader
を介してロードされ、依存関係の小さなセットは、システムの ClassLoader からロードされます。
legacy-jar
パッケージタイプを使用して本番アプリケーションを実行すると、すべてがシステム ClassLoader で読み込まれるため、完全にフラットなクラスパスとなります。
GraalVMは複数のClassLoaderを実際にサポートしていないため、フラットクラスパス戦略はGraalVMネイティブイメージにも使用されます。
その他のすべてのユースケース(テスト、開発モード、アプリケーションのビルドなど)では、Quarkus はここで説明したクラスロードアーキテクチャを使用します。
Quarkus のブートストラップ
すべての Quarkus アプリケーションは、 independent-projects/bootstrap
モジュールの QuarkusBootstrap クラスによって作成されます。このクラスは、Quarkus アプリケーションに必要なすべての関連する依存関係(デプロイメントとランタイムの両方)を解決するために使用されます。このプロセスの最終結果は、 CuratedApplication
であり、これにはアプリケーションのすべてのクラスロード情報が含まれています。
次に、 CuratedApplication
を使用して AugmentAction
インスタンスを作成し、本番アプリケーションを作成したり、ランタイムのものを起動/再起動したりすることができます。このアプリケーションインスタンスは、分離された ClassLoader 内に存在しています。収集処理によって解決されるため、クラスパスに Quarkus デプロイメントクラスを配置する必要はありません。
このブートストラッププロセスは、Quarkus がどのように起動されても、異なるパラメーターが渡されているだけで、同じであるべきです。
現在の実行モード
現在のところ、Quarkus のブートストラップには以下のようなユースケースがあります。
-
Maven による本番アプリケーションの作成
-
Maven の開発モード
-
Gradle による本番アプリケーションの作成
-
Gradle の開発モード
-
QuarkusTest (Maven、Gradle、IDE)
-
QuarkusUnitTest (Maven、Gradle、IDE)
-
QuarkusDevModeTest(Maven、Gradle、IDE)
-
Arquillian アダプター
このリファクタの目的の一つは、これらの異なる実行モードのすべてが基本的に同じ方法で Quarkus を起動できるようにすることです。
クラスローダーの実装
Quarkusには多くのClassLoadersがあります。これはそれらの関係を示しています:
各ClassLoaderの役割は以下の通りです:
- ベース ClassLoader
-
これは通常、通常の JVM System ClassLoader です。Maven のような環境では異なる場合があります。この ClassLoader はブートストラップクラスをロードするために使用され、他の ClassLoader インスタンスは JDK クラスのロードをこれに委譲します。
- オーグメンテーション ClassLoader
-
これは、すべての
-deployment
アーティファクトとその依存関係、および他のユーザー依存関係をロードします。アプリケーションのルートやホットデプロイされたコードはロードしません。この ClassLoader は永続的に動作し、アプリケーションが再起動してもそれは残ります (ホットデプロイされている可能性のあるアプリケーションクラスをロードできないのはそのためです)。親はベースの ClassLoader であり、Transformerセーフです。
現在のところ、これはベース ClassLoader に委譲するように設定することができますが、計画ではこのオプションは廃止され、常に分離された ClassLoader として使用することになっています。これを分離型 ClassLoader にすることは、すべてのビルダクラスが分離されていることを意味するので複雑です。
- デプロイメント ClassLoader
-
これはすべてのアプリケーション・クラスをロードできます。その親はオーグメンテーションクラスローダーなので、すべてのデプロイメントクラスをロードすることもできます。
この ClassLoader は永続的ではなく、アプリケーションの起動時に再作成され、分離されています。この ClassLoader は、ビルドステップを実行する際に使用されるコンテキスト ClassLoader です。また、Transformerセーフでもあります。
- ベースランタイム ClassLoader
-
これは、すべてのランタイム・エクステンションの依存関係、およびその他のユーザー依存関係をロードします(これには、オーグメンテーション・クラスローダーによってもロードされたクラスの重複コピーが含まれる可能性があることに注意してください)。 アプリケーション・ルートやホット・デプロイされたコードはロードしません。 このClassLoaderは永続的で、アプリケーションが再起動しても残ります(そのため、ホット・デプロイされたアプリケーション・クラスをロードすることはできません)。このClassLoaderの親はベースClassLoaderです。
これはホットリロードできないコードをロードしますが、変換をサポートしています (ただし、クラスがロードされるとこの変換はできなくなります)。これは、最初のアプリケーション起動時に登録された Transformer のみが有効になることを意味しますが、これらの Transformer は冪等であることが期待されているため、これは問題を引き起こすことはないはずです。ここで必要となる可能性のある変換の例として、外部 jar にパッケージ化された Panache エンティティーがあります。このクラスは静的メソッドを実装するために変換する必要がありますが、この変換は一度しか行われないため、再起動時には最初の起動時に作成されたクラスのコピーを使用します。
この ClassLoader は、拡張 ClassLoader とデプロイメント ClassLoader から分離されています。つまり、デプロイメント側の静的フィールドに値を設定して、実行時に読み込まれることを期待することはできません。これにより、開発アプリケーションやテストアプリケーションが本番アプリケーションのように振る舞うことができます(本番アプリケーションは全く新しい JVM で実行されるという点で分離されています)。
これは、実行時のバージョンは別の依存関係のセットに対してリンクされる可能性があることを意味します。例えば、デプロイ時に使用する Hibernate バージョンには ByteBuddy が含まれていても、実行時に使用するバージョンには含まれていない場合があります。
- ランタイムクラス ローダー
-
この ClassLoader は、アプリケーションクラスやその他のホットデプロイ可能なリソースをロードするために使用されます。親はベースランタイム ClassLoader で、アプリケーションの再起動時に再作成されます。
分離型クラスローダー
ランタイムの ClassLoader は常に分離されています。これは、解決された依存関係リストのほとんどすべてのクラスの独自のコピーを持つことを意味します。これには例外があります:
-
JDK クラス
-
エクステンションが親優先とマークしたアーティファクトからのクラス (これについては後述します)。
親優先の依存関係
分離された方法でロードされるべきではないクラスがいくつかありますが、それは常にシステムの ClassLoader(または Quarkus のブートストラップを担当している ClassLoader )によってロードされなければなりません。ほとんどのエクステンションはこのことを気にする必要はありませんが、必要な場合もあります:
-
ロギングはシステム ClassLoader によってロードされなければならないので、いくつかのロギング関連のクラス
-
Quarkus のブートストラップ自体
必要であれば、quarkus-extension-maven-plugin
で設定することができます。ある依存関係を親優先としてマークした場合、その依存関係もすべて親優先でなければならないことに注意してください。
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-maven-plugin</artifactId>
<configuration>
<parentFirstArtifacts>
<parentFirstArtifact>io.quarkus:quarkus-bootstrap-core</parentFirstArtifact>
<parentFirstArtifact>io.quarkus:quarkus-development-mode-spi</parentFirstArtifact>
<parentFirstArtifact>org.jboss.logmanager:jboss-logmanager</parentFirstArtifact>
<parentFirstArtifact>org.jboss.logging:jboss-logging</parentFirstArtifact>
<parentFirstArtifact>org.ow2.asm:asm</parentFirstArtifact>
</parentFirstArtifacts>
</configuration>
</plugin>
禁止された依存関係
依存関係の中には、確実に不要なものがあります。これは通常、依存関係の名前が変更されたときに起こります(例えば、smallrye-config が org.smallrye
から org.smallrye.config
にグループを変更したとき、javax
から jakarta
に名前を変更したときなどです)。 このようなアーティファクトが依存関係ツリーに残ってしまうと、Quarkusと互換性のない古いクラスがロードされる可能性があり、問題を引き起こす可能性があります。この問題に対処するために、エクステンションでは、決してロードされないアーティファクトを指定することができます。これは、pomの`quarkus-extension-maven-plugin`を変更することで行います(これにより、`quarkus-extension.properties`ファイルが生成されます)。以下のように、`excludedArtifacts`セクションを追加するだけです:
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-maven-plugin</artifactId>
<configuration>
<excludedArtifacts>
<excludedArtifact>io.smallrye:smallrye-config</excludedArtifact>
<excludedArtifact>javax.enterprise:cdi-api</excludedArtifact>
</excludedArtifacts>
</configuration>
</plugin>
これは、エクステンションがこれらのアーティファクトの新しいバージョンに依存している場合にのみ行うべきです。もしエクステンションが代替アーティファクトを依存関係として持ち込まなかった場合、 アプリケーションが必要とするクラスが見つからなくなってしまうかもしれません。
クラスローディングの設定
開発モードとテストモードでクラスローディングのいくつかの側面を設定することができます。これは、 application.properties
を使用して行うことができます。クラスローディングの設定は通常の設定とは異なり、標準のQuarkusの設定メカニズムを使用していないため(必要な時期が早すぎるため)、 application.properties
のみをサポートしていることに注意してください。以下のオプションがサポートされています。
ビルド時に固定される構成プロパティ - 他のすべての構成プロパティは実行時にオーバーライド可能
Configuration property |
タイプ |
デフォルト |
||
---|---|---|---|---|
Artifacts that are loaded in a parent first manner. This can be used to work around issues where a given class needs to be loaded by the system ClassLoader. Note that if you make a library parent first all its dependencies should generally also be parent first. Artifacts should be configured as a comma separated list of artifact ids, with the group, artifact-id and optional classifier separated by a colon.
Environment variable: Show more |
list of string |
|||
Artifacts that are loaded in the runtime ClassLoader in dev mode, so they will be dropped and recreated on change. This is an advanced option, it should only be used if you have a problem with libraries holding stale state between reloads. Note that if you use this any library that depends on the listed libraries will also need to be reloadable. This setting has no impact on production builds. Artifacts should be configured as a comma separated list of artifact ids, with the group, artifact-id and optional classifier separated by a colon.
Environment variable: Show more |
string |
|||
Artifacts that will never be loaded by the class loader, and will not be packed into the final application. This allows you to explicitly remove artifacts from your application even though they may be present on the class path. Environment variable: Show more |
list of string |
|||
Resources that should be removed/hidden from dependencies. This allows for classes and other resources to be removed from dependencies, so they are not accessible to the application. This is a map of artifact id (in the form group:artifact) to a list of resources to be removed. When running in dev and test mode these resources are hidden from the ClassLoader, when running in production mode these files are removed from the jars that contain them. Note that if you want to remove a class you need to specify the class file name. e.g. to remove Note that for technical reasons this is not supported when running with JBang. Environment variable: Show more |
Map<String,Set<String>> |
クラスやリソースを依存関係から隠す/取り除く
クラスやリソースを依存関係から隠したり、削除したりすることができます。これは上級者向けのオプションですが、ときには便利なこともあります。これは、 quarkus.class-loading.removed-resources
設定キーで行うことが可能です。例:
quarkus.class-loading.removed-resources."io.quarkus\:quarkus-integration-test-shared-library"=io/quarkus/it/shared/RemovedResource.class
これにより、 io.quarkus:quarkus-integration-test-shared-library
のアーティファクトから RemovedResource.class
のファイルが削除されます。
このオプションはクラスローディングオプションですが、生成されたアプリケーションにも影響するため、アプリケーションが作成されると、削除されたリソースにはアクセスできなくなります。
クラスバイトコードの読み取り
正しい ClassLoader
を使用することが重要です。推奨される方法は、 Thread.currentThread().getContextClassLoader()
メソッドを呼び出して取得することです。
例:
@BuildStep
GeneratedClassBuildItem instrument(final CombinedIndexBuildItem index) {
final String classname = "com.example.SomeClass";
final ClassLoader cl = Thread.currentThread().getContextClassLoader();
final byte[] originalBytecode = IoUtil.readClassAsBytes(cl, classname);
final byte[] enhancedBytecode = ... // class instrumentation from originalBytecode
return new GeneratedClassBuildItem(true, classname, enhancedBytecode));
}