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 automatically includes the resources present in META-INF/resources (the web resources) but, outside this directory, you are on your own.

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

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

追加でリソースをネイティブ実行可能ファイルに含めるには、quarkus.native.resources.includes という設定プロパティーと、リソースを除外するための 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 resources-config.json (the most common location is within src/main/resources) JSON file defining which resources should be included:

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

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

このトピックに関する詳しい情報は、 GraalVMのドキュメントに記載されています。

最後にやるべきことは、application.properties に適切な設定を追加して、設定ファイルを native-image 実行ファイルに知らせることです。

quarkus.native.additional-build-args =-H:ResourceConfigurationFiles=resources-config.json

前のスニペットでは、src/main/resources に追加されたため、ファイルのパス全体を指定するのではなく、単に resources-config.json を使用することができました。ファイルが別のディレクトリーに追加されていた場合は、適切なファイルパスを手動で指定する必要がありました。

複数のオプションはカンマで区切ることができます。例えば、次のように使用することができます。

quarkus.native.additional-build-args =\
    -H:ResourceConfigurationFiles=resources-config.json,\
    -H:ReflectionConfigurationFiles=reflection-config.json

さまざまなリソースが含まれており、追加のリフレクションの登録を担保するため。

何らかの理由で application.properties に前述の構成を追加することが望ましくない場合には、ビルドツールで効果的に同様の操作を行うように構成することも可能です。

Maven を使用する場合は、以下のような構成にすることができます。

<profiles>
    <profile>
        <id>native</id>
        <properties>
            <quarkus.package.type>native</quarkus.package.type>
            <quarkus.native.additional-build-args>-H:ResourceConfigurationFiles=resources-config.json</quarkus.native.additional-build-args>
        </properties>
    </profile>
</profiles>

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

ネイティブ実行可能ファイルを構築する際、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: javax.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 {

}

設定ファイルの使用

設定ファイルを使用してリフレクション用のクラスを登録することも可能です。

例えば、クラス com.acme.MyClass のすべてのメソッドをリフレクションのために登録するには、reflection-config.json を作成します (最も一般的な場所は src/main/resources の中です)。

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

このファイルのフォーマットの詳細については、 GraalVMのドキュメントを参照してください。

最後にやるべきことは、application.properties に適切な設定を追加して、設定ファイルを native-image 実行ファイルに知らせることです。

quarkus.native.additional-build-args =-H:ReflectionConfigurationFiles=reflection-config.json

前のスニペットでは、src/main/resources に追加されたため、ファイルのパス全体を指定するのではなく、単に reflection-config.json を使用することができました。ファイルが別のディレクトリーに追加されていた場合は、適切なファイルパスを手動で指定する必要がありました。

複数のオプションはカンマで区切ることができます。例えば、次のように使用することができます。

quarkus.native.additional-build-args =\
    -H:ResourceConfigurationFiles=resources-config.json,\
    -H:ReflectionConfigurationFiles=reflection-config.json

さまざまなリソースが含まれており、追加のリフレクションの登録を担保するため。

何らかの理由で application.properties に前述の構成を追加することが望ましくない場合には、ビルドツールで効果的に同様の操作を行うように構成することも可能です。

Maven を使用する場合は、以下のような構成にすることができます。

<profiles>
    <profile>
        <id>native</id>
        <properties>
            <quarkus.package.type>native</quarkus.package.type>
            <quarkus.native.additional-build-args>-H:ReflectionConfigurationFiles=reflection-config.json</quarkus.native.additional-build-args>
        </properties>
    </profile>
</profiles>

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

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

There are cases where the initialization of certain classes is done in a static block needs to be postponed to runtime. Typically, omitting such configuration would result in a runtime exception like the following:

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):

クラスの初期化を遅らせる必要がある場合は、--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.

この問題を解決するには、 -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> オプションを追加し、ダイナミックプロキシ設定ファイルを用意する必要があります。このファイルのフォーマットに関するすべての情報は、 GraalVMのドキュメントに記載されています。

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でのリフレクションについての詳細は こちらをご覧ください。

リソースを含む

エクステンションのコンテキストでは、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 アダプターを追加することで解決できます。

詳しくは、Loggingガイド をご覧ください。