高度なネイティブイメージのトピック
ネストされた構成プロパティ
リフレクションヒントは、Spring 事前エンジンによって構成プロパティに対して自動的に作成されます。ただし、内部クラスではないネストされた構成プロパティには、@NestedConfigurationProperty
(Javadoc) でアノテーションを付ける必要があり、そうしないと検出されず、バインドできなくなります。
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
@ConfigurationProperties(prefix = "my.properties")
public class MyProperties {
private String name;
@NestedConfigurationProperty
private final Nested nested = new Nested();
// getters / setters...
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Nested getNested() {
return this.nested;
}
}
ここで、Nested
は次のとおりです。
public class Nested {
private int number;
// getters / setters...
public int getNumber() {
return this.number;
}
public void setNumber(int number) {
this.number = number;
}
}
上記の例では、my.properties.name
および my.properties.nested.number
の構成プロパティが生成されます。nested
フィールドに @NestedConfigurationProperty
(Javadoc) アノテーションがないと、my.properties.nested.number
プロパティはネイティブイメージでバインドできません。getter メソッドにアノテーションを付けることもできます。
コンストラクターバインディングを使用する場合は、フィールドに @NestedConfigurationProperty
(Javadoc) をアノテーションする必要があります。
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
@ConfigurationProperties(prefix = "my.properties")
public class MyPropertiesCtor {
private final String name;
@NestedConfigurationProperty
private final Nested nested;
public MyPropertiesCtor(String name, Nested nested) {
this.name = name;
this.nested = nested;
}
// getters / setters...
public String getName() {
return this.name;
}
public Nested getNested() {
return this.nested;
}
}
レコードを使用する場合は、パラメーターに @NestedConfigurationProperty
(Javadoc) をアノテーションする必要があります。
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
@ConfigurationProperties(prefix = "my.properties")
public record MyPropertiesRecord(String name, @NestedConfigurationProperty Nested nested) {
}
Kotlin を使用する場合は、データクラスのパラメーターに @NestedConfigurationProperty
(Javadoc) をアノテーションする必要があります。
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.NestedConfigurationProperty
@ConfigurationProperties(prefix = "my.properties")
data class MyPropertiesKotlin(
val name: String,
@NestedConfigurationProperty val nested: Nested
)
すべての場合に public getter および setter を使用してください。そうしないと、プロパティがバインドできなくなります。 |
Spring Boot 実行可能ファイル Jar の変換
jar に AOT で生成されたアセットが含まれている限り、Spring Boot 実行可能ファイル jar をネイティブイメージに変換することができます。これは、次のようなさまざまな理由で役立ちます。
通常の JVM パイプラインを維持し、JVM アプリケーションを CI/CD プラットフォームのネイティブイメージに変換できます。
native-image
はクロスコンパイルをサポートしていないため [GitHub] (英語) 、後で異なる OS アーキテクチャに変換する OS ニュートラルな デプロイアーティファクトを保持できます。
Cloud Native Buildpacks を使用するか、GraalVM に同梱されている native-image
ツールを使用して、Spring Boot 実行可能 jar をネイティブイメージに変換できます。
実行可能 jar には、生成されたクラスや JSON ヒントファイルなど、AOT で生成されたアセットが含まれている必要があります。 |
Buildpacks の使用
Spring Boot アプリケーションは通常、Maven (mvn spring-boot:build-image
) または Gradle (gradle bootBuildImage
) 統合を通じて Cloud Native Buildpacks を使用します。ただし、pack
(英語) を使用して、AOT 処理された Spring Boot 実行可能ファイル jar をネイティブコンテナーイメージに変換することもできます。
まず、Docker デーモンが使用可能であることを確認します (詳細については、Docker を入手 (英語) を参照してください)。Linux を使用している場合は root 以外のユーザーを許可するように構成します (英語) 。
buildpacks.io のインストールガイド (英語) に従って pack
もインストールする必要があります。
myproject-0.0.1-SNAPSHOT.jar
としてビルドされた AOT 処理された Spring Boot 実行可能ファイル jar が target
ディレクトリにあると仮定して、次を実行します。
$ pack build --builder paketobuildpacks/builder-jammy-java-tiny \
--path target/myproject-0.0.1-SNAPSHOT.jar \
--env 'BP_NATIVE_IMAGE=true' \
my-application:0.0.1-SNAPSHOT
この方法でイメージを生成するために、ローカルに GraalVM をインストールする必要はありません。 |
pack
が終了したら、docker run
を使用してアプリケーションを起動できます。
$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT
GraalVM ネイティブイメージの使用
AOT で処理された Spring Boot 実行可能ファイル jar をネイティブ実行可能ファイルに変換する別のオプションは、GraalVM native-image
ツールを使用することです。これが機能するには、マシンに GraalVM ディストリビューションが必要です。Liberica ネイティブイメージキットページ (英語) に手動でダウンロードするか、SDKMAN! などのダウンロードマネージャーを使用することができます。
myproject-0.0.1-SNAPSHOT.jar
としてビルドされた AOT 処理された Spring Boot 実行可能ファイル jar が target
ディレクトリにあると仮定して、次を実行します。
$ rm -rf target/native
$ mkdir -p target/native
$ cd target/native
$ jar -xvf ../myproject-0.0.1-SNAPSHOT.jar
$ native-image -H:Name=myproject @META-INF/native-image/argfile -cp .:BOOT-INF/classes:`find BOOT-INF/lib | tr '\n' ':'`
$ mv myproject ../
これらのコマンドは Linux または macOS マシンで機能しますが、Windows 用に調整する必要があります。 |
@META-INF/native-image/argfile は jar にパッケージ化されていない場合があります。到達可能性メタデータのオーバーライドが必要な場合にのみ含まれます。 |
native-image -cp フラグはワイルドカードを受け入れません。すべての jar がリストされていることを確認する必要があります (上記のコマンドは、これを行うために find および tr を使用します)。 |
トレースエージェントの使用
GraalVM ネイティブイメージトレースエージェント (英語) を使用すると、関連するヒントを生成するために、JVM でのリフレクション、リソース、プロキシの使用をインターセプトできます。Spring は、これらのヒントのほとんどを自動的に生成しますが、トレースエージェントを使用すると、不足しているエントリをすばやく特定できます。
エージェントを使用してネイティブイメージのヒントを生成する場合、いくつかの方法があります。
アプリケーションを直接起動して実行します。
アプリケーションテストを実行して、アプリケーションを実行します。
最初のオプションは、ライブラリまたはパターンが Spring によって認識されない場合に、欠落しているヒントを特定できます。
2 番目のオプションは、反復可能なセットアップにはより魅力的に聞こえますが、デフォルトでは、生成されたヒントにはテストインフラストラクチャで必要なものがすべて含まれます。これらのいくつかは、アプリケーションが実際に実行されるときに不要になります。この問題に対処するために、エージェントは、生成された出力から特定のデータを除外するアクセスフィルターファイルをサポートします。
アプリケーションを直接起動する
次のコマンドを使用して、ネイティブイメージトレースエージェントがアタッチされたアプリケーションを起動します。
$ java -Dspring.aot.enabled=true \
-agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ \
-jar target/myproject-0.0.1-SNAPSHOT.jar
これで、ヒントが必要なコードパスを実行してから、ctrl-c
でアプリケーションを停止できます。
アプリケーションのシャットダウン時に、ネイティブイメージトレースエージェントは、指定された構成出力ディレクトリにヒントファイルを書き込みます。これらのファイルを手動でインスペクションするか、ネイティブイメージビルドプロセスへの入力として使用できます。入力として使用するには、src/main/resources/META-INF/native-image/
ディレクトリにコピーします。次回ネイティブイメージをビルドするときに、GraalVM はこれらのファイルを考慮します。
ネイティブイメージトレースエージェントで設定できる、より高度なオプションがあります。たとえば、記録されたヒントを呼び出し元クラスでフィルタリングするなどです。詳細については、公式ドキュメント (英語) を参照してください。
カスタムヒント
リフレクション、リソース、直列化、プロキシの使用などについて独自のヒントを提供する必要がある場合は、RuntimeHintsRegistrar
(Javadoc) API を使用できます。RuntimeHintsRegistrar
(Javadoc) インターフェースを実装するクラスを作成し、提供された RuntimeHints
(Javadoc) インスタンスを適切に呼び出します。
import java.lang.reflect.Method;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.util.ReflectionUtils;
public class MyRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// Register method for reflection
Method method = ReflectionUtils.findMethod(MyClass.class, "sayHello", String.class);
hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
// Register resources
hints.resources().registerPattern("my-resource.txt");
// Register serialization
hints.serialization().registerType(MySerializableClass.class);
// Register proxy
hints.proxies().registerJdkProxy(MyInterface.class);
}
}
その後、任意の @Configuration
(Javadoc) クラス (たとえば、@SpringBootApplication
(Javadoc) アノテーションが付けられたアプリケーションクラス) で @ImportRuntimeHints
(Javadoc) を使用して、それらのヒントをアクティブ化できます。
バインディングが必要なクラスがある場合 (主に JSON を直列化または逆直列化するときに必要)、任意の Bean で @RegisterReflectionForBinding
を使用できます。ヒントのほとんどは、たとえば @RestController
(Javadoc) メソッドからデータを受け取るときや返すときなど、自動的に推論されます。ただし、WebClient
(Javadoc) 、RestClient
(Javadoc) 、または RestTemplate
(Javadoc) を直接操作する場合は、@RegisterReflectionForBinding
(Javadoc) を使用する必要がある場合があります。
カスタムヒントのテスト
RuntimeHintsPredicates
(Javadoc) API はヒントをテストするために使用できます。この API は、RuntimeHints
(Javadoc) インスタンスをテストするために使用できる Predicate
(標準 Javadoc) を構築するメソッドを提供します。
AssertJ を使用している場合、テストは次のようになります。
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.boot.docs.packaging.nativeimage.advanced.customhints.MyRuntimeHints;
import static org.assertj.core.api.Assertions.assertThat;
class MyRuntimeHintsTests {
@Test
void shouldRegisterHints() {
RuntimeHints hints = new RuntimeHints();
new MyRuntimeHints().registerHints(hints, getClass().getClassLoader());
assertThat(RuntimeHintsPredicates.resource().forResource("my-resource.txt")).accepts(hints);
}
}
静的にヒントを提供する
必要に応じて、カスタムヒントを 1 つ以上の GraalVM JSON ヒントファイルに静的に提供できます。このようなファイルは、META-INF/native-image/*/*/
ディレクトリ内の src/main/resources/
に配置する必要があります。AOT 処理中に生成されたヒントは、 META-INF/native-image/{groupId}/{artifactId}/
という名前のディレクトリに書き込まれます。静的ヒントファイルは、この場所と競合しないディレクトリ ( META-INF/native-image/{groupId}/{artifactId}-additional-hints/
など) に配置します。
既知の制限
GraalVM ネイティブイメージは進化するテクノロジーであり、すべてのライブラリがサポートを提供するわけではありません。GraalVM コミュニティは、まだ独自のものを提供していないプロジェクトに到達可能性メタデータ [GitHub] (英語) を提供することで支援しています。Spring 自体には、サードパーティライブラリのヒントが含まれておらず、代わりに到達可能性メタデータプロジェクトに依存しています。
Spring Boot アプリケーションのネイティブイメージを生成する際に問題が発生した場合は、Spring Boot wiki の GraalVM を使用した Spring Boot [GitHub] (英語) ページを確認してください。GitHub の spring-aot-smoke-tests [GitHub] (英語) プロジェクトに課題を投稿することもできます。これは、一般的なアプリケーション型が期待どおりに動作していることを確認するために使用されます。
GraalVM で動作しないライブラリを見つけた場合は、到達可能性メタデータプロジェクト [GitHub] (英語) で課題を提起してください。