高度なネイティブイメージのトピック

ネストされた構成プロパティ

リフレクションヒントは、Spring 事前エンジンによって構成プロパティに対して自動的に作成されます。ただし、内部クラスではないネストされた構成プロパティには、@NestedConfigurationProperty でアノテーションを付ける必要があります。そうしないと、検出されず、バインドできません。

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 アノテーションがないと、ネイティブイメージで my.properties.nested.number プロパティをバインドできません。

コンストラクターバインディングを使用する場合は、フィールドに @NestedConfigurationProperty でアノテーションを付ける必要があります。

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 でアノテーションを付ける必要があります。

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 でアノテーションを付ける必要があります。

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 をネイティブイメージに変換することができます。これは、次のようなさまざまな理由で役立ちます。

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-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 API を使用できます。RuntimeHintsRegistrar インターフェースを実装するクラスを作成し、提供された RuntimeHints インスタンスを適切に呼び出します。

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 クラス (たとえば、@SpringBootApplication アノテーション付きアプリケーションクラス) で @ImportRuntimeHints を使用して、これらのヒントを有効にすることができます。

バインディングが必要なクラス (主に JSON を直列化または逆直列化するときに必要) がある場合は、任意の Bean で @RegisterReflectionForBinding を使用できます。ヒントのほとんどは、たとえば @RestController メソッドからデータを受け取ったり返したりするときに、自動的に推論されます。ただし、WebClientRestClient または RestTemplate を直接操作する場合は、@RegisterReflectionForBinding の使用が必要になる場合があります。

カスタムヒントのテスト

RuntimeHintsPredicates API を使用して、ヒントをテストできます。API は、RuntimeHints インスタンスのテストに使用できる Predicate を構築するメソッドを提供します。

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);
	}

}

既知の制限

GraalVM ネイティブイメージは進化するテクノロジーであり、すべてのライブラリがサポートを提供するわけではありません。GraalVM コミュニティは、まだ独自のものを提供していないプロジェクトに到達可能性メタデータ [GitHub] (英語) を提供することで支援しています。Spring 自体には、サードパーティライブラリのヒントが含まれておらず、代わりに到達可能性メタデータプロジェクトに依存しています。

Spring Boot アプリケーションのネイティブイメージを生成する際に問題が発生した場合は、Spring Boot wiki の GraalVM を使用した Spring Boot [GitHub] (英語) ページを確認してください。GitHub の spring-aot-smoke-tests [GitHub] (英語) プロジェクトに課題を投稿することもできます。これは、一般的なアプリケーション型が期待どおりに動作していることを確認するために使用されます。

GraalVM で動作しないライブラリを見つけた場合は、到達可能性メタデータプロジェクト [GitHub] (英語) で課題を提起してください。