ネイティブ・イメージでのリソースへのアクセス

デフォルトでは、native-imageツールでは、生成時にクラスパスにあるリソースは最終イメージに統合されません。Class.getResource()Class.getResourceAsStream()などのコール(または対応するClassLoaderメソッド)で特定のリソース(nullではなく)を返すには、実行時にアクセス可能なリソースを指定する必要があります。これは、次の内容で構成ファイルを使用して実現できます:

{
  "resources": {
    "includes": [
      {"pattern": "<Java regexp that matches resource(s) to be included in the image>"},
      {"pattern": "<another regexp>"},
      ...
    ],
    "excludes": [
      {"pattern": "<Java regexp that matches resource(s) to be excluded from the image>"},
      {"pattern": "<another regexp>"},
      ...
    ]
  }
}

構成ファイルのパスは、-H:ResourceConfigurationFiles=/path/to/resource-config.jsonを使用してnative-imageに渡す必要があります。または、native-imageに対して個々のリソース・パスを直接指定することもできます:

native-image -H:IncludeResources="<Java regexp that matches resources to be included in the image>" -H:ExcludeResources="<Java regexp that matches resources to be excluded from the image>" ...

-H:IncludeResourcesおよび-H:ExcludeResourcesオプションを複数回渡して、それぞれリソースを包含または除外するための複数の正規表現を定義できます。

ネイティブ実行可能ファイルに含まれるリソースを確認するには、オプション-H:Log=registerResource:<log level>を使用します。<log level>は、1から5の範囲(詳細が最も低いレベルから最も高いレベル)である必要があります。

使用例

次のプロジェクト構造を考えます:

my-app-root
└── src
    ├── main
    │   └── com.my.app
    │       ├── Resource0.txt
    │       └── Resource1.txt
    └── resources
        ├── Resource2.txt
        └── Resource3.txt

この場合、次のようになります:

次のデモでは、ネイティブ実行可能ファイルにリソースを含める方法について説明します。アプリケーションfortuneは、従来のfortune UNIXプログラムをシミュレートします(詳細は、fortuneを参照)。

  1. 次のJavaコードをFortune.javaファイルに保存します:

     import java.io.BufferedReader;
     import java.io.InputStreamReader;
     import java.util.ArrayList;
     import java.util.Random;
     import java.util.Scanner;
    
     public class Fortune {
    
         private static final String SEPARATOR = "%";
         private static final Random RANDOM = new Random();
         private ArrayList<String> fortunes = new ArrayList<>();
    
         public Fortune(String path) {
             // Scan the file into the array of fortunes
             Scanner s = new Scanner(new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream(path))));
             s.useDelimiter(SEPARATOR);
             while (s.hasNext()) {
                 fortunes.add(s.next());
             }        
         }
            
         private void printRandomFortune() throws InterruptedException {
             int r = RANDOM.nextInt(fortunes.size()); //Pick a random number
             String f = fortunes.get(r);  //Use the random number to pick a random fortune
             for (char c: f.toCharArray()) {  // Print out the fortune
               System.out.print(c);
                 Thread.sleep(100);   
             }
         }
          
         public static void main(String[] args) throws InterruptedException {
             Fortune fortune = new Fortune("/fortunes.u8");
             fortune.printRandomFortune();
         }
     }
    
  2. fortunes.u8リソース・ファイルを開き、Fortune.javaと同じディレクトリに保存します。

  3. コンパイルします:

     $JAVA_HOME/bin/javac Fortune.java
    
  4. リソース・パスを指定してネイティブの実行可能ファイルをビルドします:

     $JAVA_HOME/bin/native-image Fortune -H:IncludeResources=".*u8$"
    
  5. 実行可能ファイルのイメージを実行します:

     ./fortune
    

Javaリソースおよびその他の動的機能の構成支援に関するガイドも参照してください。

ロケール

イメージに含めるロケールと、デフォルトにするロケールを指定することもできます。たとえば、デフォルトのロケールをスイス・ドイツ語に切り替え、さらにフランス語と英語も含めるには、次のホスト型オプションを使用できます。

native-image -Duser.country=CH -Duser.language=de -H:IncludeLocales=fr,en

ロケールは言語タグを使用して指定します。-H:+IncludeAllLocalesを使用してすべてのロケールを含めることもできますが、そうすると結果のバイナリのサイズが大きくなることに注意してください。

ネイティブ・イメージのリソース・バンドル

Javaローカライゼーション・サポート(java.util.ResourceBundle)を使用すると、JavaコードでL10Nリソースをロードし、時間のロケールやフォーマットなどの実際のランタイム設定に適したユーザー・メッセージを表示できます。

ネイティブ・イメージでは、生成されたバイナリでの使用に適したバンドルをロードおよび格納できるように、アプリケーションで必要なリソース・バンドルの事前認識が必要になります。バンドルは、リソース構成ファイル(前述の説明を参照)のbundlesセクションで指定できます:

{
  "bundles": [
    {"name":"your.pkg.Bundle"},
    {"name":"another.pkg.Resource"},
    {"name":"etc.Bundle"}
  ],
  "resources": <see above>
}

または、次のようにバンドルをnative-imageのオプションとして直接指定することもできます:

native-image -H:IncludeResourceBundles=your.pgk.Bundle,another.pkg.Resource,etc.Bundle ...

デフォルトでは、要求されたすべてのロケールについて、要求されたバンドルが含められます。これを最適化するために、ロケール固有の部分文字列でIncludeResourceBundlesを使用できます。たとえば、-H:+IncludeResourceBundles=com.company.bundles.MyBundle_fr-FRでは、フランス語のバンドルのみが含められます。

Javaモジュールのリソース

リソースが<Java regexp that matches resources to be included in the image>で指定されているか、リソース・バンドルがバンドル名で指定されている場合、これらのリソースまたはバンドルが取得する必要があるモジュールを正確に指定できます。これを行うには、resource-regexまたはバンドル名の前にmodule-nameを指定し、セパレータとして:を指定します。たとえば:

{
   "resources": {
      "includes": [
         {
            "pattern": "library-module:^resource-file.txt$"
         }
      ]
   },
   "bundles": [
      {"name":"main-module:your.pkg.Bundle"}
   ]
}

これにより、native-imageには、Javaモジュールlibrary-moduleからのresource-file.txtのみが含まれます。そのため、他のモジュールまたはクラスパスにパターン^resource-file.txt$に一致するリソースが含まれている場合でも、モジュールlibrary-module内のリソースのみがimage-inclusionに登録されます。同じバンドル名your.pkg.Bundleでアクセス可能な他のバンドルがある場合も同様で、main-moduleのバンドルのみが含まれます。また、ネイティブ・イメージでは、モジュールがイメージ実行時にアクセス可能であることが保証されます。つまり、次のコード・パターン:

InputStream resource = ModuleLayer.boot().findModule(moduleName).getResourceAsStream(resourcePath);

は、前述のように登録されたリソースに対して常に期待どおりに機能します(モジュールに静的分析で到達可能と思われるコードが含まれていない場合でも)。

ローカライゼーションのJVMモード

リソース・バンドル・ルックアップは、JVMの多くのインフラストラクチャを利用する複雑で動的なメカニズムです。その結果、HelloWorldなどの小さいアプリケーションに対して実行可能ファイルのサイズが大きくなります。このため、デフォルトで最適化モードが設定され、すべてのバンドルが事前に認識されているという事実を利用してこのルックアップが簡略化されます。元のJVMルックアップを使用する場合は、-H:-LocalizationOptimizedModeオプションを使用します。