GraalVM ネイティブイメージ (英語) は、コンパイル済みの Java アプリケーションを事前に処理することによって生成できるスタンドアロンの実行可能ファイルです。通常、ネイティブイメージはメモリフットプリントが小さく、対応する JVM よりも高速に起動します。

1. GraalVM ネイティブイメージの導入

GraalVM ネイティブイメージは、Java アプリケーションをデプロイして実行する新しい方法を提供します。Java 仮想マシンと比較して、ネイティブイメージはより少ないメモリフットプリントで実行でき、はるかに高速な起動時間で実行できます。

これらは、コンテナーイメージを使用してデプロイされるアプリケーションに適しており、"Function as a service" (FaaS) プラットフォームと組み合わせると特に興味深いものになります。

JVM 用に作成された従来のアプリケーションとは異なり、GraalVM ネイティブイメージアプリケーションでは、実行可能ファイルを作成するために事前処理が必要です。この事前処理には、メインエントリポイントからアプリケーションコードを静的に分析することが含まれます。

GraalVM ネイティブイメージは、プラットフォーム固有の完全な実行可能ファイルです。ネイティブイメージを実行するために Java 仮想マシンを提供する必要はありません。

GraalVM を使い始めて実験したいだけの場合は、"初めての GraalVM ネイティブアプリケーションの開発" セクションまでスキップして、後でこのセクションに戻ることができます。

1.1. JVM デプロイ との主な違い

GraalVM ネイティブイメージが事前に生成されるという事実は、ネイティブアプリケーションと JVM ベースのアプリケーションの間にいくつかの重要な違いがあることを意味します。主な違いは次のとおりです。

  • アプリケーションの静的分析は、ビルド時に main エントリポイントから実行されます。

  • ネイティブイメージの作成時に到達できないコードは削除され、実行可能ファイルの一部にはなりません。

  • GraalVM はコードの動的要素を直接認識しないため、リフレクション、リソース、直列化、動的プロキシについて通知する必要があります。

  • アプリケーションのクラスパスはビルド時に固定され、変更できません。

  • 遅延クラスの読み込みはありません。実行可能ファイルに同梱されているものはすべて、起動時にメモリに読み込まれます。

  • 完全にサポートされていない Java アプリケーションのいくつかの側面に関して、いくつかの制限があります。

これらの違いに加えて、Spring は Spring 事前処理と呼ばれるプロセスを使用しており、これによりさらに制限が課されます。これらについて学ぶために、少なくとも次のセクションの最初を必ず参照してください。

GraalVM リファレンスドキュメントのネイティブイメージの互換性ガイド (英語) セクションでは、GraalVM の制限事項について詳しく説明しています。

1.2. Spring 事前処理について

典型的な Spring Boot アプリケーションは非常に動的であり、構成は実行時に実行されます。実際、Spring Boot 自動構成の概念は、正しく構成するためにランタイムの状態に反応することに大きく依存しています。

アプリケーションのこれらの動的な特徴について GraalVM に伝えることは可能ですが、そうすると静的分析の利点のほとんどが台無しになります。代わりに、Spring Boot を使用してネイティブイメージを作成する場合、閉じた世界が想定され、アプリケーションの動的な側面が制限されます。

閉じた世界の前提には、GraalVM 自体によって作成される制限に加えて、次の制限が含まれます。

  • アプリケーションで定義された Bean は実行時に変更できません。つまり、次のことを意味します。

    • Spring @Profile アノテーションとプロファイル固有の構成には制限があります

    • Bean が作成された場合に変更されるプロパティはサポートされていません (たとえば、@ConditionalOnProperty および .enable プロパティ)。

これらの制限が適用されると、ビルド時に Spring が事前処理を実行し、GraalVM が使用できる追加のアセットを生成することが可能になります。Spring AOT で処理されたアプリケーションは、通常、次のものを生成します。

  • Java ソースコード

  • バイトコード (動的プロキシなど)

  • GraalVM JSON ヒントファイル:

    • リソースのヒント (resource-config.json)

    • リフレクションのヒント (reflect-config.json)

    • 直列化のヒント (serialization-config.json)

    • Java プロキシのヒント (proxy-config.json)

    • JNI ヒント (jni-config.json)

1.2.1. ソースコード生成

Spring アプリケーションは Spring Bean で構成されます。内部的に、Spring Framework は 2 つの異なる概念を使用して Bean を管理します。Bean インスタンスは、実際に作成されたインスタンスのことで、他の Bean に注入することができるものです。Bean の属性とそのインスタンスの作成方法を定義するために使用される Bean 定義もあります。

典型的な @Configuration クラスを取ると:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyConfiguration {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }

}

Bean 定義は、@Configuration クラスを解析し、@Bean メソッドを見つけることによって作成されます。上記の例では、myBean という名前のシングルトン Bean の BeanDefinition を定義しています。MyConfiguration クラス自体の BeanDefinition も作成しています。

myBean インスタンスが必要な場合、Spring は、myBean() メソッドを呼び出して結果を使用する必要があることを認識します。JVM で実行している場合、アプリケーションの起動時に @Configuration クラスの解析が行われ、リフレクションを使用して @Bean メソッドが呼び出されます。

ネイティブイメージを作成する場合、Spring は別の方法で動作します。実行時に @Configuration クラスを解析して Bean 定義を生成するのではなく、ビルド時に実行します。Bean 定義が検出されると、それらは処理され、GraalVM コンパイラーで分析できるソースコードに変換されます。

Spring AOT プロセスは、上記の構成クラスを次のようなコードに変換します。

import org.springframework.beans.factory.aot.BeanInstanceSupplier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;

/**
 * Bean definitions for {@link MyConfiguration}.
 */
public class MyConfiguration__BeanDefinitions {

    /**
     * Get the bean definition for 'myConfiguration'.
     */
    public static BeanDefinition getMyConfigurationBeanDefinition() {
        Class<?> beanType = MyConfiguration.class;
        RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
        beanDefinition.setInstanceSupplier(MyConfiguration::new);
        return beanDefinition;
    }

    /**
     * Get the bean instance supplier for 'myBean'.
     */
    private static BeanInstanceSupplier<MyBean> getMyBeanInstanceSupplier() {
        return BeanInstanceSupplier.<MyBean>forFactoryMethod(MyConfiguration.class, "myBean")
            .withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(MyConfiguration.class).myBean());
    }

    /**
     * Get the bean definition for 'myBean'.
     */
    public static BeanDefinition getMyBeanBeanDefinition() {
        Class<?> beanType = MyBean.class;
        RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
        beanDefinition.setInstanceSupplier(getMyBeanInstanceSupplier());
        return beanDefinition;
    }

}
生成される正確なコードは、Bean 定義の性質によって異なる場合があります。

上記で、生成されたコードが @Configuration クラスと同等の Bean 定義を作成することがわかりますが、GraalVM が理解できる直接的な方法で作成されます。

myConfiguration Bean の Bean 定義と myBean の定義があります。myBean インスタンスが必要な場合は、BeanInstanceSupplier が呼び出されます。このサプライヤーは、myConfiguration Bean で myBean() メソッドを呼び出します。

Spring AOT 処理中に、アプリケーションは Bean 定義が使用可能になるまで開始されます。Bean インスタンスは、AOT 処理フェーズでは作成されません。

Spring AOT は、すべての Bean 定義に対してこのようなコードを生成します。また、Bean 後処理が必要な場合 (たとえば、@Autowired メソッドを呼び出す場合) にコードを生成します。AOT 処理されたアプリケーションが実際に実行されるときに ApplicationContext を初期化するために Spring Boot によって使用される ApplicationContextInitializer も生成されます。

AOT で生成されたソースコードは冗長になる可能性がありますが、非常に読みやすく、アプリケーションのデバッグ時に役立ちます。生成されたソースファイルは、Maven および build/generated/aotSources を Gradle と共に使用する場合、target/spring-aot/main/sources にあります。

1.2.2. ヒントファイルの生成

Spring AOT エンジンは、ソースファイルの生成に加えて、GraalVM で使用されるヒントファイルも生成します。ヒントファイルには、GraalVM がコードを直接調べても理解できないことを処理する方法を説明する JSON データが含まれています。

例: プライベートメソッドで Spring アノテーションを使用している可能性があります。Spring は、GraalVM であっても、プライベートメソッドを呼び出すためにリフレクションを使用する必要があります。このような状況が発生した場合、Spring はリフレクションヒントを記述して、プライベートメソッドが直接呼び出されなくても、ネイティブイメージで利用できる必要があることを GraalVM が認識できるようにします。

ヒントファイルは META-INF/native-image に生成され、GraalVM によって自動的に取得されます。

生成されたヒントファイルは、Maven および build/generated/aotResources を Gradle と共に使用する場合、target/spring-aot/main/resources にあります。

1.2.3. プロキシクラスの生成

Spring は、作成したコードを追加機能で強化するために、プロキシクラスを生成する必要がある場合があります。これを行うために、バイトコードを直接生成する cglib ライブラリを使用します。

アプリケーションが JVM で実行されている場合、プロキシクラスはアプリケーションの実行時に動的に生成されます。ネイティブイメージを作成する場合、これらのプロキシをビルド時に作成して、GraalVM に含めることができるようにする必要があります。

ソースコードの生成とは異なり、生成されたバイトコードは、アプリケーションのデバッグ時には特に役に立ちません。ただし、javap などのツールを使用して .class ファイルの内容をインスペクションする必要がある場合は、Maven の場合は target/spring-aot/main/classes、Gradle の場合は build/generated/aotClasses で見つけることができます。

2. 初めての GraalVM ネイティブアプリケーションの開発

GraalVM ネイティブイメージと Spring 事前エンジンのしくみの概要を理解したところで、アプリケーションの作成方法を見ていきましょう。

Spring Boot ネイティブイメージアプリケーションを構築するには、主に次の 2 つのメソッドがあります。

  • Cloud Native Buildpacks の Spring Boot サポートを使用して、ネイティブ実行可能ファイルを含む軽量コンテナーを生成します。

  • GraalVM ネイティブビルドツールを使用してネイティブ実行可能ファイルを生成します。

新しいネイティブ Spring Boot プロジェクトを開始する最も簡単な方法は、start.spring.io に移動し、"GraalVM Native Support" 依存関係を追加してプロジェクトを生成することです。含まれている HELP.md ファイルは、開始のヒントを提供します。

2.1. サンプルアプリケーション

ネイティブイメージの作成に使用できるサンプルアプリケーションが必要です。ここでは、単純な "Hello World!" を使用します。"getting-started.html" セクションで説明されている Web アプリケーションで十分です。

要約すると、メインのアプリケーションコードは次のようになります。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class MyApplication {

    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}

このアプリケーションは、Spring MVC と組み込みの Tomcat を使用します。どちらも、GraalVM ネイティブイメージで動作することがテストおよび検証されています。

2.2. Buildpacks を使用したネイティブイメージの構築

Spring Boot には、Maven と Gradle の両方のネイティブイメージを直接サポートする buildpack が含まれています。これは、単一のコマンドを入力するだけで、ローカルで実行されている Docker デーモンに適切なイメージをすばやく取得できることを意味します。結果のイメージには JVM は含まれず、代わりにネイティブイメージが静的にコンパイルされます。これにより、イメージが小さくなります。

イメージに使用されるビルダーは paketobuildpacks/builder-jammy-tiny:latest です。フットプリントが小さく、攻撃対象領域が少なくなりますが、必要に応じてイメージで使用できるツールを増やすために paketobuildpacks/builder-jammy-base:latest または paketobuildpacks/builder-jammy-full:latest を使用することもできます。

2.2.1. システム要件

Docker をインストールする必要があります。詳細については、Docker を入手 (英語) を参照してください。Linux を使用している場合は root 以外のユーザーを許可するように構成します (英語)

docker run hello-world (sudo なし)を実行して、Docker デーモンが期待どおりに到達可能であることを確認できます。詳細については、Maven または Gradle Spring Boot プラグインのドキュメントを確認してください。
macOS では、Docker に割り当てられたメモリを少なくとも 8GB に増やし、場合によっては CPU も追加することをお勧めします。詳細については、このスタックオーバーフローの回答 (英語) を参照してください。Microsoft Windows では、パフォーマンスを向上させるために Docker WSL2 バックエンド (英語) を有効にしてください。

2.2.2. Maven の使用

Maven を使用してネイティブイメージコンテナーを構築するには、pom.xml ファイルで spring-boot-starter-parent および org.graalvm.buildtools:native-maven-plugin を使用するようにする必要があります。次のような <parent> セクションが必要です。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.2</version>
</parent>

さらに、これを <build> <plugins> セクションに含める必要があります。

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
</plugin>

spring-boot-starter-parent は、ネイティブイメージを作成するために実行する必要がある実行を構成する native プロファイルを宣言します。コマンドラインで -P フラグを使用して、プロファイルをアクティブ化できます。

spring-boot-starter-parent を使用したくない場合は、Spring Boot のプラグインから process-aot ゴールの実行を構成し、Native Build Tools プラグインから add-reachability-metadata ゴールを構成する必要があります。

イメージをビルドするには、native プロファイルをアクティブにして spring-boot:build-image ゴールを実行します。

$ mvn -Pnative spring-boot:build-image

2.2.3. Gradle の使用

Spring Boot Gradle プラグインは、GraalVM Native Image プラグインが適用されると、AOT タスクを自動的に構成します。Gradle ビルドに org.graalvm.buildtools.native を含む plugins ブロックが含まれていることを確認する必要があります。

org.graalvm.buildtools.native プラグインが適用されている限り、bootBuildImage タスクは JVM ではなくネイティブイメージを生成します。以下を使用してタスクを実行できます。

$ gradle bootBuildImage

2.2.4. サンプルの実行

適切なビルドコマンドを実行すると、Docker イメージが利用可能になります。docker run を使用してアプリケーションを開始できます。

$ docker run --rm -p 8080:8080 docker.io/library/myproject:0.0.1-SNAPSHOT

次のような出力が表示されます。

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v3.2.2)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)
起動時間はマシンごとに異なりますが、JVM で実行されている Spring Boot アプリケーションよりもはるかに高速です。

Web ブラウザーを localhost:8080 で開くと、次の出力が表示されるはずです。

Hello World!

アプリケーションを正常に終了するには、ctrl-c を押します。

2.3. ネイティブビルドツールを使用したネイティブイメージのビルド

Docker を使用せずにネイティブ実行可能ファイルを直接生成する場合は、GraalVM Native Build Tools を使用できます。ネイティブビルドツールは、Maven と Gradle の両方に対して GraalVM が提供するプラグインです。使用して、ネイティブイメージの生成など、さまざまな GraalVM タスクを実行できます。

2.3.1. 前提条件

ネイティブビルドツールを使用してネイティブイメージをビルドするには、マシンに GraalVM ディストリビューションが必要です。Liberica ネイティブイメージキットページ (英語) で手動でダウンロードするか、SDKMAN! などのダウンロードマネージャーを使用できます。

Linux と macOS

macOS または Linux にネイティブイメージコンパイラーをインストールするには、SDKMAN! を使用することをお勧めします。次のコマンドを使用して、sdkman.io (英語) から SDKMAN! を取得し、Liberica GraalVM ディストリビューションをインストールします。

$ sdk install java 22.3.r17-nik
$ sdk use java 22.3.r17-nik

java -version の出力をチェックして、正しいバージョンが構成されていることを確認します。

$ java -version
openjdk version "17.0.5" 2022-10-18 LTS
OpenJDK Runtime Environment GraalVM 22.3.0 (build 17.0.5+8-LTS)
OpenJDK 64-Bit Server VM GraalVM 22.3.0 (build 17.0.5+8-LTS, mixed mode)
Windows

Windows では、次の手順 (英語) に従って、バージョン 22.3 の GraalVM (英語) または Liberica ネイティブイメージキット (英語) 、Visual Studio ビルドツール、および Windows SDK をインストールします。Windows 関連のコマンドラインの最大長 のため、Maven または Gradle プラグインを実行するには、通常の Windows コマンドラインではなく、必ず x64 ネイティブツールコマンドプロンプトを使用してください。

2.3.2. Maven の使用

buildpack サポートと同様に、native プロファイルを継承するために spring-boot-starter-parent を使用していること、および org.graalvm.buildtools:native-maven-plugin プラグインが使用されていることを確認する必要があります。

native プロファイルがアクティブな状態で、native:compile ゴールを呼び出して native-image コンパイルをトリガーできます。

$ mvn -Pnative native:compile

ネイティブイメージの実行可能ファイルは、target ディレクトリにあります。

2.3.3. Gradle の使用

Native Build Tools Gradle プラグインがプロジェクトに適用されると、Spring Boot Gradle プラグインが Spring AOT エンジンを自動的にトリガーします。タスクの依存関係は自動的に構成されるため、標準の nativeCompile タスクを実行するだけでネイティブイメージを生成できます。

$ gradle nativeCompile

ネイティブイメージの実行可能ファイルは、build/native/nativeCompile ディレクトリにあります。

2.3.4. サンプルの実行

この時点で、アプリケーションは機能するはずです。アプリケーションを直接実行して開始できるようになりました。

Maven
$ target/myproject
Gradle
$ build/native/nativeCompile/myproject

次のような出力が表示されます。

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v3.2.2)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 0.08 seconds (process running for 0.095)
起動時間はマシンごとに異なりますが、JVM で実行されている Spring Boot アプリケーションよりもはるかに高速です。

Web ブラウザーを localhost:8080 で開くと、次の出力が表示されるはずです。

Hello World!

アプリケーションを正常に終了するには、ctrl-c を押します。

3. GraalVM ネイティブイメージのテスト

ネイティブイメージアプリケーションを作成する場合は、可能な限り引き続き JVM を使用して、ユニットテストと統合テストの大部分を開発することをお勧めします。これにより、開発者のビルド時間を短縮し、既存の IDE 統合を使用できるようになります。JVM の広範なテストカバレッジにより、異なる可能性が高い領域にネイティブイメージテストを集中させることができます。

ネイティブイメージのテストでは、通常、次の側面が機能することを確認します。

  • Spring AOT エンジンはアプリケーションを処理でき、AOT 処理モードで実行されます。

  • GraalVM には、有効なネイティブイメージを生成できることを確認するのに十分なヒントがあります。

3.1. JVM を使用した事前処理のテスト

Spring Boot アプリケーションが実行されると、それがネイティブイメージとして実行されているかどうかを検出しようとします。ネイティブイメージとして実行されている場合は、ビルド時に Spring AOT エンジンによって生成されたコードを使用してアプリケーションを初期化します。

アプリケーションが通常の JVM で実行されている場合、AOT によって生成されたコードは無視されます。

native-image コンパイルフェーズが完了するまでに時間がかかる場合があるため、アプリケーションを JVM で実行し、AOT で生成された初期化コードを使用すると便利な場合があります。これにより、AOT で生成されたコードにエラーがないこと、およびアプリケーションが最終的にネイティブイメージに変換されたときに欠落がないことをすばやく検証できます。

JVM で Spring Boot アプリケーションを実行し、AOT 生成コードを使用するには、spring.aot.enabled システムプロパティを true に設定します。

例:

$ java -Dspring.aot.enabled=true -jar myapplication.jar
テストしている jar に AOT 生成コードが含まれていることを確認する必要があります。Maven の場合、これは、-Pnative でビルドして native プロファイルをアクティブにする必要があることを意味します。Gradle の場合、ビルドに org.graalvm.buildtools.native プラグインが含まれていることを確認する必要があります。

spring.aot.enabled プロパティを true に設定してアプリケーションを起動すると、ネイティブイメージに変換したときに動作するという確信が高まります。

実行中のアプリケーションに対して統合テストを実行することも検討できます。例: Spring WebClient を使用して、アプリケーションの REST エンドポイントを呼び出すことができます。または、Selenium などのプロジェクトを使用して、アプリケーションの HTML レスポンスを確認することを検討することもできます。

3.2. ネイティブビルドツールを使用したテスト

GraalVM ネイティブビルドツールには、ネイティブイメージ内でテストを実行する機能が含まれています。これは、アプリケーションの内部が GraalVM ネイティブイメージで機能することを深くテストしたい場合に役立ちます。

実行するテストを含むネイティブイメージを生成する操作は時間がかかる可能性があるため、ほとんどの開発者はおそらく JVM をローカルで使用することを好みます。ただし、CI パイプラインの一部として非常に役立つ場合があります。例: ネイティブテストを 1 日 1 回実行することを選択する場合があります。

Spring Framework には、テストを実行するための事前サポートが含まれています。通常の Spring Test 機能はすべて、ネイティブイメージテストで動作します。例: @SpringBootTest アノテーションを引き続き使用できます。Spring Boot テストスライスを使用して、アプリケーションの特定の部分のみをテストすることもできます。

Spring Framework のネイティブテストサポートは、次のように機能します。

  • 必要な ApplicationContext インスタンスを検出するために、テストが分析されます。

  • これらのアプリケーションコンテキストのそれぞれに事前処理が適用され、アセットが生成されます。

  • ネイティブイメージが作成され、生成されたアセットが GraalVM によって処理されます。

  • ネイティブイメージには、検出されたテストのリストで構成された JUnit TestEngine も含まれています。

  • ネイティブイメージが開始され、各テストを実行して結果をレポートするエンジンがトリガーされます。

3.2.1. Maven の使用

Maven を使用してネイティブテストを実行するには、pom.xml ファイルで spring-boot-starter-parent が使用されていることを確認してください。次のような <parent> セクションが必要です。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.2</version>
</parent>

spring-boot-starter-parent は、ネイティブテストの実行に必要な実行を構成する nativeTest プロファイルを宣言します。コマンドラインで -P フラグを使用して、プロファイルをアクティブ化できます。

spring-boot-starter-parent を使用したくない場合は、Spring Boot プラグインから process-test-aot ゴールの実行を構成し、Native Build Tools プラグインから test ゴールを構成する必要があります。

イメージをビルドしてテストを実行するには、nativeTest プロファイルをアクティブにして test ゴールを使用します。

$ mvn -PnativeTest test

3.2.2. Gradle の使用

Spring Boot Gradle プラグインは、GraalVM ネイティブイメージプラグインが適用されると、AOT テストタスクを自動的に構成します。Gradle ビルドに org.graalvm.buildtools.native を含む plugins ブロックが含まれていることを確認する必要があります。

Gradle を使用してネイティブテストを実行するには、nativeTest タスクを使用できます。

$ gradle nativeTest

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

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

リフレクションヒントは、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 を使用してください。そうしないと、プロパティがバインドできなくなります。

4.2. Spring Boot 実行可能ファイル Jar の変換

jar に AOT で生成されたアセットが含まれている限り、Spring Boot 実行可能ファイル jar をネイティブイメージに変換することができます。これは、次のようなさまざまな理由で役立ちます。

Cloud Native Buildpacks を使用するか、GraalVM に同梱されている native-image ツールを使用して、Spring Boot 実行可能 jar をネイティブイメージに変換できます。

実行可能 jar には、生成されたクラスや JSON ヒントファイルなど、AOT で生成されたアセットが含まれている必要があります。

4.2.1. 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

4.2.2. 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 を使用します)。

4.3. トレースエージェントの使用

GraalVM ネイティブイメージトレースエージェント (英語) を使用すると、関連するヒントを生成するために、JVM でのリフレクション、リソース、プロキシの使用をインターセプトできます。Spring は、これらのヒントのほとんどを自動的に生成しますが、トレースエージェントを使用すると、不足しているエントリをすばやく特定できます。

エージェントを使用してネイティブイメージのヒントを生成する場合、いくつかの方法があります。

  • アプリケーションを直接起動して実行します。

  • アプリケーションテストを実行して、アプリケーションを実行します。

最初のオプションは、ライブラリまたはパターンが Spring によって認識されない場合に、欠落しているヒントを特定できます。

2 番目のオプションは、反復可能なセットアップにはより魅力的に聞こえますが、デフォルトでは、生成されたヒントにはテストインフラストラクチャで必要なものがすべて含まれます。これらのいくつかは、アプリケーションが実際に実行されるときに不要になります。この問題に対処するために、エージェントは、生成された出力から特定のデータを除外するアクセスフィルターファイルをサポートします。

4.3.1. アプリケーションを直接起動する

次のコマンドを使用して、ネイティブイメージトレースエージェントがアタッチされたアプリケーションを起動します。

$ 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 はこれらのファイルを考慮します。

ネイティブイメージトレースエージェントで設定できる、より高度なオプションがあります。たとえば、記録されたヒントを呼び出し元クラスでフィルタリングするなどです。詳細については、公式ドキュメント (英語) を参照してください。

4.4. カスタムヒント

リフレクション、リソース、直列化、プロキシの使用などについて独自のヒントを提供する必要がある場合は、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 の使用が必要になる場合があります。

4.4.1. カスタムヒントのテスト

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

}

4.5. 既知の制限

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

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

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

5. 次のステップ

ビルドプラグインによって提供される事前処理の詳細については、Maven および Gradle プラグインのドキュメントを参照してください。処理を実行するために使用される API の詳細については、Spring Framework ソースの org.springframework.aot.generate および org.springframework.beans.factory.aot パッケージを参照してください。

Spring と GraalVM の既知の制限については、Spring Boot ウィキ [GitHub] (英語) を参照してください。

次のセクションでは、Spring Boot CLI について説明します。