The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

ネイティブ・アプリケーションを作成するためのヒント

このガイドには、Java アプリケーションをネイティブ実行可能ファイルとして実行しようとしたときに発生する可能性のある問題を回避するためのさまざまなヒントやコツが記載されています。

適用されるソリューションが異なる可能性のある 2 つの文脈を区別していることに注意してください。

  • アプリケーションの文脈では、pom.xml を修正して、 native-image 設定を調整することに依存することになります。

  • エクステンションの文脈では、Quarkus はこれらすべてを簡素化するための多くのインフラストラクチャーを提供しています。

コンテキストに応じて適切なセクションを参照してください。

アプリケーションでのネイティブのサポート

GraalVM は多くの制約を課しており、アプリケーションをネイティブ実行可能なものにするには、いくつかの調整が必要になるかもしれません。

リソースのインクルード

デフォルトでは、ネイティブ実行可能ファイルをビルドする際に、GraalVM はクラスパス上にあるリソースを、作成するネイティブ実行可能ファイルには含めません。ネイティブ実行可能ファイルの一部となるリソースは、明示的に設定する必要があります。

Quarkus では、META-INF/resources (Web リソース) にあるリソースを自動的にインクルードしますが、このディレクトリー以外の場所は自分で設定する必要があります。

META-INF/resources にあるものはすべて、静的な Web リソースとして公開されるので、ここでは非常に注意が必要です。このディレクトリーは、「これらのリソースを自動的にネイティブ実行可能ファイルに含める」ためのショートカットではなく、静的な Web リソースのためにのみ使用する必要があります。

その他のリソースは明示的に宣言する必要があります。

quarkus.native.resources.includes 設定プロパティの使用

より多くのリソースをネイティブ実行可能ファイルに含めるには、quarkus.native.resources.includes という設定プロパティーと、quarkus.native.resources.excludes リソースを除外するための quarkus.native.resources.excludes を使用するのが最も簡単です。

どちらの設定プロパティーもグロブパターンに対応しています。

たとえば、application.properties で以下のプロパティーを設定すると、

quarkus.native.resources.includes=foo/**,bar/**/*.txt
quarkus.native.resources.excludes=foo/private/**

以下が含まれます。

  • foo/ ディレクトリーとそのサブディレクトリーにあるすべてのファイルのうち、foo/private/ とそのサブディレクトリーにあるファイルを除く。

  • bar/ ディレクトリーとそのサブディレクトリーにあるすべてのテキストファイル。

設定ファイルの使用

If globs are not sufficiently precise for your use case and you need to rely on regular expressions, or if you prefer relying on the GraalVM infrastructure, you can also create a resource-config.json JSON file defining which resources should be included. This, and other native image configuration files, should be placed under the src/main/resources/META-INF/native-image/<group-id>/<artifact-id> folder. This way they will be automatically parsed by the native build, without additional configuration.

GraalVMインフラストラクチャに依存するということは、新しいMandrelとGraalVMのバージョンがリリースされるたびに、設定ファイルを最新の状態に保つ責任があるということです。

Please also note that the resource-config.json file will be overwritten by Quarkus if placed directly under src/main/resources/META-INF/native-image/ as Quarkus generates its own configuration file in this directory.

このようなファイルの例を以下に示します:

{
  "resources": [
    {
      "pattern": ".*\\.xml$"
    },
    {
      "pattern": ".*\\.json$"
    }
  ]
}

このパターンは有効な Java 正規表現です。ここでは、すべての XML ファイルと JSONファイル をネイティブ実行可能ファイルに含めています。

このトピックの詳細については、 ネイティブ・イメージでリソースにアクセスするGraalVM ガイドを参照してください。

リフレクションのための登録

ネイティブ実行可能ファイルを構築する際、GraalVM はクローズな環境を想定して動作します。これは、コールツリーを解析し、直接使用しないクラス/メソッド/フィールドをすべて削除します。

リフレクションで使用される要素はコールツリーの一部ではないので、デッドコードとして排除されます (他のケースでは直接呼び出されない場合)。ネイティブ実行可能ファイルにこれらの要素を含めるには、リフレクションに明示的に登録する必要があります。

これは非常によくあるケースです。JSONライブラリは一般的にリフレクションを使用してオブジェクトをJSONにシリアライズする為です。

    public class Person {
        private String first;
        private String last;

        public String getFirst() {
            return first;
        }

        public void setFirst(String first) {
            this.first = first;
        }

        public String getLast() {
            return last;
        }

        public void setValue(String last) {
            this.last = last;
        }
    }

    @Path("/person")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public class PersonResource {

        private final Jsonb jsonb;

        public PersonResource() {
            jsonb = JsonbBuilder.create(new JsonbConfig());
        }

        @GET
        public Response list() {
            return Response.ok(jsonb.fromJson("{\"first\":  \"foo\", \"last\":  \"bar\"}", Person.class)).build();
        }
    }

上記のコードを使用した場合、ネイティブ実行可能ファイルを使用すると以下のような例外が発生します:

Exception handling request to /person: org.jboss.resteasy.spi.UnhandledException: jakarta.json.bind.JsonbException: Can't create instance of a class: class org.acme.jsonb.Person, No default constructor found

あるいはJackson を使用している場合は以下の通りです:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.acme.jsonb.Person and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

さらに厄介なのは、例外がスローされずに JSON の結果が完全に空になってしまうことです。

このタイプの問題を解決するには、2 つの方法があります。

@RegisterForReflection アノテーションの使用

リフレクション用のクラスを登録する最も簡単な方法は、@RegisterForReflection アノテーションを使用することです。

@RegisterForReflection
public class MyClass {
}

クラスがサードパーティの jar にある場合は、そのクラスの @RegisterForReflection をホストする空のクラスを使用することで行うことができます。

@RegisterForReflection(targets={ MyClassRequiringReflection.class, MySecondClassRequiringReflection.class})
public class MyReflectionConfiguration {
}

なお、 MyClassRequiringReflectionMySecondClassRequiringReflection はリフレクションに登録されますが、 MyReflectionConfiguration は登録されません。

この機能は、オブジェクトマッピング機能を使用するサードパーティのライブラリ(JacksonやGSONなど)を使用する際に便利です。

@RegisterForReflection(targets = {User.class, UserImpl.class})
public class MyReflectionConfiguration {

}

設定ファイルの使用

また、GraalVMインフラストラクチャに依存することを好むなら、リフレクションのためにクラスを登録するために設定ファイルを使用することもできます。

GraalVMインフラストラクチャに依存するということは、新しいMandrelとGraalVMのバージョンがリリースされるたびに、設定ファイルを最新の状態に保つ責任があるということです。

As an example, in order to register all methods of class com.acme.MyClass for reflection, we create reflect-config.json (the most common location is within src/main/resources)

[
  {
    "name" : "com.acme.MyClass",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true,
    "allDeclaredFields" : true,
    "allPublicFields" : true
  }
]

このファイルのフォーマットの詳細については、 ネイティブイメージにおけるGraalVMリフレクション ガイドを参照してください。

The final order of business is to make the configuration file known to the native-image executable. To do that, place the configuration file under the src/main/resources/META-INF/native-image/<group-id>/<artifact-id> folder. This way they will be automatically parsed by the native build, without additional configuration.

クラスの初期化を遅らせる

デフォルトでは、Quarkus はビルド時にすべてのクラスを初期化します。

特定のクラスの初期化が静的ブロックで行われる場合、実行時に延期する必要がある場合があります。通常、このような設定を省略すると、以下のような実行時例外が発生します。

Error: No instances are allowed in the image heap for a class that is initialized or reinitialized at image runtime: sun.security.provider.NativePRNG
Trace: object java.security.SecureRandom
method com.amazonaws.services.s3.model.CryptoConfiguration.<init>(CryptoMode)
Call path from entry point to com.amazonaws.services.s3.model.CryptoConfiguration.<init>(CryptoMode):

もう一つのよくあるエラーの原因は、GraalVMによって取得されたイメージヒープが Random / SplittableRandom インスタンスを含んでいる場合です。

Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected an instance of Random/SplittableRandom class in the image heap. Instances created during image generation have cached seed values and don't behave as expected.

これは、Quarkus がビルド時に静的な Random/SplittableRandom フィールドを持つクラスを初期化し、この特定のインスタンスがイメージヒープに一時的に含まれることが原因である場合がほとんどです。

この Random/SplittableRandom の問題については、 このブログ記事 に詳細な情報があります。

これらの場合、実行時に違反しているクラスの初期化を遅らせることが解決策になる可能性があり、それを達成するには、 --initialize-at-run-time=<package or class> 設定ノブを使用できます。

上記の例のように、quarkus.native.additional-build-args 設定プロパティーを使用して native-image 設定に追加する必要があります。

詳細については、 ネイティブ・イメージにおけるGraalVMクラスの初期化 ガイドを参照してください。

quarkus.native.additional-build-args 設定プロパティーを介して複数のクラスやパッケージを指定する必要がある場合は、, シンボルをエスケープする必要があります。その例を以下に示します。

quarkus.native.additional-build-args=--initialize-at-run-time=com.example.SomeClass\\,org.acme.SomeOtherClass

application.properties の代わりに Maven 設定を使用する場合。

<quarkus.native.additional-build-args>--initialize-at-run-time=com.example.SomeClass\,org.acme.SomeOtherClass</quarkus.native.additional-build-args>

プロキシークラスの管理

ネイティブアプリケーションを書く際には、実装するインターフェースのリストを指定して、イメージのビルド時にプロキシークラスを定義する必要があります。

このような状況では、以下のようなエラーが発生します。

com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.apache.http.conn.HttpClientConnectionManager, interface org.apache.http.pool.ConnPoolControl, interface com.amazonaws.http.conn.Wrapped] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.

Solving this issue requires creating a proxy-config.json file under the src/main/resources/META-INF/native-image/<group-id>/<artifact-id> folder. This way the configuration will be automatically parsed by the native build, without additional configuration. For more information about the format of this file, see the Dynamic Proxy Metadata in JSON documentation.

モジュール化のメリット

ネイティブ実行可能ファイルのビルド時に、GraalVM はアプリケーションのコールツリーを分析し、必要なすべてのコードを含むコードセットを生成します。モジュラーコードベースを使用することは、アプリケーションの未使用部分またはオプション部分の問題を回避すると同時に、ネイティブ実行可能ファイルのビルド時間とサイズの両方を削減するための鍵となります。このセクションでは、ネイティブアプリケーションのモジュール化の利点の背後にある詳細について学習します。

コードが十分にモジュール化されていない場合、生成されたネイティブ実行可能ファイルには、ユーザーが必要とする以上のコードが含まれてしまうことがあります。機能が使用されず、コードがネイティブ実行ファイルにコンパイルされる場合、ネイティブコンパイル時間とメモリ使用量、さらにネイティブ実行ファイルのディスクスペースと起動ヒープサイズの無駄となります。さらに、ネイティブコンパイルやランタイムエラーを引き起こすサードパーティライブラリや特殊なAPIサブシステムが使用され、それらの使用が十分にモジュール化されていない場合には、さらに問題が発生します。最近の問題としては、JavaのAWT APIを使用して画像を含むXMLファイルをデシリアライズできるJAXBライブラリが挙げられます。Quarkus XMLユーザーの大半は画像をデシリアライズする必要がないため、ネイティブの実行ファイルにJAXB AWTコードを追加するようQuarkusを特別に設定しない限り、ユーザーアプリケーションにJava AWTコードを含める必要はないはずです。しかし、AWTを使用するJAXBコードは、残りのXMLパースコードと同じjarにあるため、この分離を達成するのはかなり複雑で、Javaバイトコード置換を使用して回避する必要がありました。このような置換はメンテナンスが難しく、簡単に壊れてしまうので、最後の手段であるべきです。

モジュール式のコードベースは、この種の問題を回避するための最良の方法です。上記の JAXB/AWT の問題を考えると、イメージを扱う JAXB コードが別のモジュールまたは jar (例: jaxb-images) にあった場合、Quarkus は、構築時に画像を含むXMLファイルをシリアライズ/デシリアライズする必要があるとユーザーが特に要求しない限り、そのモジュールを含めないように選択することができます。

モジュラーアプリケーションのもう 1 つの利点は、ネイティブ実行可能ファイルに組み込む必要があるコードセットを削減できることです。コードセットが小さいほど、ネイティブ実行可能ファイルのビルドが高速になり、生成されるネイティブ実行可能ファイルが小さくなります。

ここで重要なポイントは次のとおりです。オプションの機能、特に大きなフットプリントを持つサードパーティーのライブラリーや API サブシステムに依存する機能は、別のモジュールに保持することが最善のソリューションです。

アプリケーションに同様の問題があるかどうかを確認するにはどうすればよいですか? アプリケーションの詳細な調査は別として、 https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.htmlMaven のオプションの依存関係 の使用法を見つけることは、アプリケーションで同様の問題が発生する可能性があります。この種の依存関係は避けるべきであり、代わりに、オプションの依存関係と対話するコードは別のモジュールに移動する必要があります。

シングルトンの強制

As already explained in the delay class initialization section, Quarkus marks all code to be initialized at build time by default. This means that, unless marked otherwise, static variables will be assigned at build time, and static blocks will be executed at build time too.

これにより、通常は実行ごとに異なるJavaプログラムの値が、常に一定の値として返されるようになります。例えば、 System.currentTimeMillis() という値が割り当てられている静的フィールドは、Quarkus ネイティブ実行ファイルとして実行されると、常に同じ値を返します。

静的変数の初期化に依存するシングルトンは、同様の問題に悩まされます。たとえば、クエリーを実行する REST エンドポイントと共に、静的初期化に基づくシングルトンがあるとします。

@Path("/singletons")
public class Singletons {

    @GET
    @Path("/static")
    public long withStatic() {
        return StaticSingleton.startTime();
    }
}

class StaticSingleton {
    static final long START_TIME = System.currentTimeMillis();

    static long startTime() {
        return START_TIME;
    }
}

singletons/static のエンドポイントに問い合わせると、アプリケーションを再起動しても常に同じ値が返されます。

$ curl http://localhost:8080/singletons/static
1656509254532%

$ curl http://localhost:8080/singletons/static
1656509254532%

### Restart the native application ###

$ curl http://localhost:8080/singletons/static
1656509254532%

enum クラスに依存しているシングルトンも同じ問題の影響を受けます。

@Path("/singletons")
public class Singletons {

    @GET
    @Path("/enum")
    public long withEnum() {
        return EnumSingleton.INSTANCE.startTime();
    }
}

enum EnumSingleton {
    INSTANCE(System.currentTimeMillis());

    private final long startTime;

    private EnumSingleton(long startTime) {
        this.startTime = startTime;
    }

    long startTime() {
        return startTime;
    }
}

singletons/enum エンドポイントがクエリーされると、アプリケーションが再起動された後でも、常に同じ値が返されます。

$ curl http://localhost:8080/singletons/enum
1656509254601%

$ curl http://localhost:8080/singletons/enum
1656509254601%

### Restart the native application ###

$ curl http://localhost:8080/singletons/enum
1656509254601%

これを修正する 1 つの方法は、CDI の @Singleton アノテーションを使用してシングルトンを構築することです。

@Path("/singletons")
public class Singletons {

    @Inject
    CdiSingleton cdiSingleton;

    @GET
    @Path("/cdi")
    public long withCdi() {
        return cdiSingleton.startTime();
    }
}

@Singleton
class CdiSingleton {
    // Note that the field is not static
    final long startTime = System.currentTimeMillis();

    long startTime() {
        return startTime;
    }
}

再起動するたびに、 singletons/cdi をクエリすると、JVM モードと同じように、異なる値が返却されます:

$ curl http://localhost:8080/singletons/cdi
1656510218554%

$ curl http://localhost:8080/singletons/cdi
1656510218554%

### Restart the native application ###

$ curl http://localhost:8080/singletons/cdi
1656510714689%

静的フィールドや列挙型に依存しながらシングルトンを強制する別の方法は、実行時までクラス初期化を遅延させる ことです。CDIベースのシングルトンの良い利点は、クラスの初期化が制約されないので、ユースケースに応じて、ビルド時または実行時に初期化されるべきかを自由に決定できることです。

よくある Java API オーバーライドの注意点

特定の一般的に使用される Java メソッドは、toStringequalshashCode などのユーザークラスによってオーバーライドされます。オーバーライドの大部分は問題を引き起こしませんが、サードパーティーのライブラリー (追加の書式設定など) を使用したり、動的言語機能 (リフレクションやプロキシーなど) を使用したりすると、ネイティブイメージのビルドが失敗する可能性があります。これらの障害の中には、設定によって解決できるものもありますが、処理がより難しいものもあります。

GraalVM ポイントツー分析の観点からは、アプリケーションが明示的に呼び出さなくても、これらのメソッドオーバーライドで何が起こるかが問題になります。これは、これらのメソッドが JDK 全体で使用されているためであり、これらの呼び出しの 1 つが制約のない型 (たとえば、java.lang.Object) で実行されるだけで、分析でその特定のすべての実装をプルする必要があるためです。方法。

Quarkus エクステンションでネイティブをサポートする

Quarkus のエクステンションでのネイティブのサポートは、Quarkus がこれらすべてを単純化するための多くのツールを提供しているため、さらに簡単になりました。

ここで説明したことはすべて、Quarkus エクステンションの文脈でのみ機能し、アプリケーションでは機能しません。

リフレクションの登録

Quarkus では、ReflectiveClassBuildItem を使用することで、エクステンションへのリフレクションの登録が簡単になり、JSON 設定ファイルが不要になります。

リフレクション用のクラスを登録するには、Quarkus プロセッサークラスを作成し、リフレクションを登録するビルドステップを追加する必要があります。

public class SaxParserProcessor {

    @BuildStep
    ReflectiveClassBuildItem reflection() {
        // since we only need reflection to the constructor of the class, we can specify `false` for both the methods and the fields arguments.
        return new ReflectiveClassBuildItem(false, false, "com.sun.org.apache.xerces.internal.parsers.SAXParser");
    }

}

GraalVMにおけるリフレクションの詳細については、 ネイティブイメージにおけるGraalVM リフレクション ガイドを参照してください。

リソースのインクルード

エクステンションのコンテキストでは、Quarkus は、エクステンションの作成者が NativeImageResourceBuildItem を指定できるようにすることで、JSON 設定ファイルの必要性を排除しています。

public class ResourcesProcessor {

    @BuildStep
    NativeImageResourceBuildItem nativeImageResourceBuildItem() {
        return new NativeImageResourceBuildItem("META-INF/extra.properties");
    }

}

ネイティブ実行可能ファイルでのGraalVMリソースの取り扱いについては、 ネイティブ・イメージでのGraalVMリソース・アクセス ガイドを参照してください。

遅延クラス初期化

Quarkus では、エクステンションの作成者が簡単に RuntimeInitializedClassBuildItem を登録できるようにすることで、物事をシンプルにしています。簡単な例としては、次のようなものが考えられます。

public class S3Processor {

    @BuildStep
    RuntimeInitializedClassBuildItem cryptoConfiguration() {
        return new RuntimeInitializedClassBuildItem(CryptoConfiguration.class.getCanonicalName());
    }

}

このような構文を使用すると、 --initialize-at-run-time オプションが自動的に native-image コマンドラインに追加されることを意味します。

--initialize-at-run-time オプションの詳細については、 ネイティブ・イメージにおけるGraalVMクラスの初期化 ガイドを参照してください。

プロキシークラスの管理

同様に、Quarkus では、エクステンションの作成者が NativeImageProxyDefinitionBuildItem を登録することができます。その例としては、以下のようなものがあります。

public class S3Processor {

    @BuildStep
    NativeImageProxyDefinitionBuildItem httpProxies() {
        return new NativeImageProxyDefinitionBuildItem("org.apache.http.conn.HttpClientConnectionManager",
                "org.apache.http.pool.ConnPoolControl", "com.amazonaws.http.conn.Wrapped");
    }

}

このような構文を使用すると、-H:DynamicProxyConfigurationResources オプションが自動的に native-image コマンドラインに追加されることを意味します。

プロキシ・クラスの詳細については、 GraalVM による動的プロキシの手動設定 ガイドを参照してください。

ネイティブイメージでのロギング

Apache Commons Logging や Log4j などのロギングコンポーネントを必要とする依存関係を使用していて、ネイティブ実行可能ファイルをビルドする際に ClassNotFoundException が発生する場合、ロギングライブラリーを除外し、対応する JBoss Logging アダプターを追加することで解決できます。

For more details please refer to the Logging guide.

関連コンテンツ