戻る

ネイティブイメージMavenプラグインを使用した到達可能性メタデータの追加

Javaアプリケーションからネイティブ実行ファイルを**Maven**でビルドできます。そのためには、ネイティブビルドツールプロジェクトの一部として提供されるGraalVMネイティブイメージMavenプラグインを使用します。

実際のJavaアプリケーションでは、Javaリフレクションオブジェクトが必要になる場合や、ネイティブコードを呼び出す場合、またはクラスパス上のリソースにアクセスする場合があります。これらは、native-imageツールがビルド時に認識する必要があり、メタデータという形式で提供される動的な機能です。(ネイティブイメージは、クラスをビルド時に動的にロードし、実行時にはロードしません。)

アプリケーションの依存関係によっては、メタデータを提供する方法は3つあります。

  1. GraalVM到達可能性メタデータリポジトリの使用
  2. トレーシングエージェントの使用
  3. 自動検出(必要なリソースがクラスパス上のsrc/main/resources/ディレクトリに直接ある場合)

このガイドでは、GraalVM到達可能性メタデータリポジトリトレーシングエージェントを使用してネイティブ実行ファイルをビルドする方法を示します。このガイドの目的は、2つのアプローチの違いを示し、到達可能性メタデータの使用が開発タスクを簡素化する方法を示すことです。

手順に従ってアプリケーションを作成することをお勧めします。または、完成した例に直接進むこともできます。

デモアプリケーションの準備

前提条件

GraalVM JDKがインストールされていることを確認してください。SDKMAN!を使用するのが最も簡単な方法です。他のインストールオプションについては、ダウンロードセクションをご覧ください。

  1. お好みのIDEまたはコマンドラインから、org.graalvm.exampleパッケージに「H2Example」という名前の新しいJavaプロジェクトをMavenで作成します。

  2. メインクラスファイル(src/main/java/org/graalvm/example/H2Example.java)を開き、その内容を以下に置き換えます。
     package org.graalvm.example;
    
     import java.sql.Connection;
     import java.sql.DriverManager;
     import java.sql.PreparedStatement;
     import java.sql.ResultSet;
     import java.sql.SQLException;
     import java.util.ArrayList;
     import java.util.Comparator;
     import java.util.HashSet;
     import java.util.List;
     import java.util.Set;
    
     public class H2Example {
    
         public static final String JDBC_CONNECTION_URL = "jdbc:h2:./data/test";
    
         public static void main(String[] args) throws Exception {
             // Cleanup
             withConnection(JDBC_CONNECTION_URL, connection -> {
                 connection.prepareStatement("DROP TABLE IF EXISTS customers").execute();
                 connection.commit();
             });
    
             Set<String> customers = Set.of("Lord Archimonde", "Arthur", "Gilbert", "Grug");
    
             System.out.println("=== Inserting the following customers in the database: ");
             printCustomers(customers);
    
             // Insert data
             withConnection(JDBC_CONNECTION_URL, connection -> {
                 connection.prepareStatement("CREATE TABLE customers(id INTEGER AUTO_INCREMENT, name VARCHAR)").execute();
                 PreparedStatement statement = connection.prepareStatement("INSERT INTO customers(name) VALUES (?)");
                 for (String customer : customers) {
                     statement.setString(1, customer);
                     statement.executeUpdate();
                 }
                 connection.commit();
             });
    
             System.out.println("");
             System.out.println("=== Reading customers from the database.");
             System.out.println("");
    
             Set<String> savedCustomers = new HashSet<>();
             // Read data
             withConnection(JDBC_CONNECTION_URL, connection -> {
                 try (ResultSet resultSet = connection.prepareStatement("SELECT * FROM customers").executeQuery()) {
                     while (resultSet.next()) {
                         savedCustomers.add(resultSet.getObject(2, String.class));
                     }
                 }
             });
    
             System.out.println("=== Customers in the database: ");
             printCustomers(savedCustomers);
         }
    
         private static void printCustomers(Set<String> customers) {
             List<String> customerList = new ArrayList<>(customers);
             customerList.sort(Comparator.naturalOrder());
             int i = 0;
             for (String customer : customerList) {
                 System.out.println((i + 1) + ". " + customer);
                 i++;
             }
         }
    
         private static void withConnection(String url, ConnectionCallback callback) throws SQLException {
             try (Connection connection = DriverManager.getConnection(url)) {
                 connection.setAutoCommit(false);
                 callback.run(connection);
             }
         }
    
         private interface ConnectionCallback {
             void run(Connection connection) throws SQLException;
         }
     }
    
  3. H2Example/src/test/java/ディレクトリ(存在する場合)を削除します。

  4. プロジェクト設定ファイル(pom.xml)を開き、その内容を以下に置き換えます。
     <?xml version="1.0" encoding="UTF-8"?>
     <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
             http://maven.apache.org/xsd/maven-4.0.0.xsd">
         <modelVersion>4.0.0</modelVersion>
    
         <groupId>org.graalvm.buildtools.examples</groupId>
         <artifactId>maven</artifactId>
         <version>1.0.0-SNAPSHOT</version>
    
         <properties>
             <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
             <h2.version>2.2.220</h2.version>
             <!-- Replace with your Java version -->
             <java.version>22</java.version>
             <imageName>h2example</imageName>
             <mainClass>org.graalvm.example.H2Example</mainClass>
         </properties>
    
         <dependencies>
             <!-- 1. H2 Database dependency -->
             <dependency>
                 <groupId>com.h2database</groupId>
                 <artifactId>h2</artifactId>
                 <version>${h2.version}</version>
             </dependency>
         </dependencies>
         <!-- 2. Native Image Maven plugin within a Maven profile -->
         <profiles>
             <profile>
                 <id>native</id>
                 <build>
                     <plugins>
                         <plugin>
                             <groupId>org.graalvm.buildtools</groupId>
                             <artifactId>native-maven-plugin</artifactId>
                             <version>0.10.1</version>
                             <extensions>true</extensions>
                             <executions>
                                 <execution>
                                     <id>build-native</id>
                                     <goals>
                                         <goal>compile-no-fork</goal>
                                     </goals>
                                     <phase>package</phase>
                                 </execution>
                             </executions>
                             <configuration>
                                 <buildArgs>
                                     <!-- 3. Quick build mode -->
                                     <buildArg>-Ob</buildArg>
                                 </buildArgs>
                             </configuration>
                         </plugin>
                     </plugins>
                 </build>
             </profile>
         </profiles>
         <build>
             <finalName>${project.artifactId}</finalName>
             <plugins>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-surefire-plugin</artifactId>
                     <version>3.0.0-M5</version>
                 </plugin>
    
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-compiler-plugin</artifactId>
                     <version>3.11.0</version>
                     <configuration>
                         <source>${java.version}</source>
                         <target>22</target>
                     </configuration>
                 </plugin>
    
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-jar-plugin</artifactId>
                     <version>3.3.0</version>
                     <configuration>
                         <archive>
                             <manifest>
                                 <addClasspath>true</addClasspath>
                                 <mainClass>${mainClass}</mainClass>
                             </manifest>
                         </archive>
                     </configuration>
                 </plugin>
    
                 <plugin>
                     <groupId>org.codehaus.mojo</groupId>
                     <artifactId>exec-maven-plugin</artifactId>
                     <version>3.1.1</version>
                     <executions>
                         <execution>
                             <id>java</id>
                             <goals>
                                 <goal>java</goal>
                             </goals>
                             <configuration>
                                 <mainClass>${mainClass}</mainClass>
                             </configuration>
                         </execution>
                         <execution>
                             <id>native</id>
                             <goals>
                                 <goal>exec</goal>
                             </goals>
                             <configuration>
                                 <executable>${project.build.directory}/${imageName}</executable>
                                 <workingDirectory>${project.build.directory}</workingDirectory>
                             </configuration>
                         </execution>
                     </executions>
                 </plugin>
             </plugins>
         </build>
    
     </project>
    

    1 Java用のオープンソースSQLデータベースであるH2データベースへの依存関係を追加します。アプリケーションはJDBCドライバを介してこのデータベースと対話します。

    2 packageフェーズにアタッチされたMavenプロファイル内でネイティブイメージMavenプラグインを有効にします。(Mavenプロファイルを使用してネイティブ実行ファイルをビルドします。)Mavenプロファイルを使用すると、JARファイルのみをビルドするか、ネイティブ実行ファイルをビルドするかを選択できます。このプラグインは、native-imageに渡す必要があるJARファイルと、実行可能ファイルのメインクラスを検出します。

    3 <buildArgs>セクションを使用して、基礎となるnative-imageビルドツールにパラメータを渡すことができます。個々の<buildArg>タグでは、コマンドラインと同じ方法でパラメータを渡すことができます。開発中のみ推奨されるクイックビルドモードを有効にする-Obオプションは、例として使用されています。他の構成オプションについては、プラグインのドキュメントを参照してください。

  5. (オプション)アプリケーションをビルドします。リポジトリのルートディレクトリから、次のコマンドを実行します。
    mvn clean package
    

    これにより、「実行可能」JARファイルが生成されます。これは、アプリケーションのすべての依存関係と正しく構成されたMANIFESTファイルを含むファイルです。

GraalVM到達可能性メタデータリポジトリを使用したネイティブ実行ファイルのビルド

ネイティブイメージMavenプラグインは、GraalVM到達可能性メタデータリポジトリをサポートしています。このリポジトリは、デフォルトでGraalVMネイティブイメージをサポートしていないライブラリのGraalVM構成を提供します。このアプリケーションが依存しているH2データベースもその1つです。サポートは明示的に有効にする必要があります。

  1. pom.xmlを開き、GraalVM到達可能性メタデータリポジトリを有効にするために、nativeプロファイルの<configuration>要素に以下を含めます。
     <metadataRepository>
         <enabled>true</enabled>
     </metadataRepository>
    

    構成ブロックは次のようになります。

     <configuration>
         <buildArgs>
             <buildArg>-Ob</buildArg>
         </buildArgs>
         <metadataRepository>
             <enabled>true</enabled>
         </metadataRepository>
     </configuration>
    

    プラグインはリポジトリからメタデータを自動的にダウンロードします。

  2. これで、プロファイルを使用してネイティブ実行ファイルをビルドできます(プロファイル名は-Pフラグで指定することに注意してください)。
     mvn package -Pnative
    

    これにより、target/ディレクトリにプラットフォーム用のネイティブ実行ファイルh2exampleが生成されます。

  3. ネイティブ実行ファイルからアプリケーションを実行します。

     ./target/h2example 
    

    アプリケーションは、H2データベースに保存されている顧客のリストを返します。

トレーシングエージェントを使用したネイティブ実行ファイルのビルド

native-imageのメタデータ構成を提供する2番目の方法は、コンパイル時にトレーシングエージェント(以降エージェント)を挿入することです。エージェントはデフォルトで無効になっていますが、pom.xmlファイルまたはコマンドラインで有効にできます。

エージェントは3つのモードで実行できます。

  • 標準:条件なしでメタデータ収集します。ネイティブ実行ファイルをビルドする場合に推奨されます。(デフォルト)
  • 条件付き:条件付きでメタデータ収集します。ネイティブ共有ライブラリを作成し、後で使用する場合はこれが推奨されます。
  • 直接:上級者向けです。このモードでは、エージェントに渡されるコマンドラインを直接制御できます。

トレーシングエージェントを使用してメタデータを取得し、提供された構成を適用してネイティブ実行ファイルをビルドする方法を以下に示します。

  1. nativeプロファイルの<configuration>要素に以下を追加して、エージェントを有効にします。
     <agent>
         <enabled>true</enabled>
     </agent>
    

    構成ブロックは次のようになります。

     <configuration>
         <agent>
             <enabled>true</enabled>
         </agent>
         <buildArgs>
             <buildArg>-Ob</buildArg>
         </buildArgs>
         <metadataRepository>
             <enabled>true</enabled>
         </metadataRepository>
     </configuration>
    
  2. エージェントを使用してアプリケーションを実行するには、Javaプロセスのフォーキングを許可する別のMOJO実行を構成する必要があります。native Mavenプロファイルセクションにexec-maven-pluginプラグインを追加します。
     <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>exec-maven-plugin</artifactId>
         <version>3.1.1</version>
         <executions>
             <execution>
                 <id>java-agent</id>
                 <goals>
                     <goal>exec</goal>
                 </goals>
                 <phase>test</phase>
                 <configuration>
                     <executable>java</executable>
                     <workingDirectory>${project.build.directory}</workingDirectory>
                     <arguments>
                         <argument>-classpath</argument>
                         <classpath/>
                         <argument>${mainClass}</argument>
                     </arguments>
                 </configuration>
             </execution>
         </executions>
     </plugin>
    
  3. JVMでエージェントを有効にしてアプリケーションを実行します。
     mvn -Pnative -Dagent=true -DskipTests -DskipNativeBuild=true package exec:exec@java-agent
    

    エージェントは、H2データベースへの呼び出しと、テスト実行中に検出されたすべての動的な機能を、target/native/agent-output/main/ディレクトリの複数の*-config.jsonファイルにキャプチャして記録します。

  4. エージェントによって収集された構成を使用してネイティブ実行ファイルをビルドします。
     mvn -Pnative -Dagent=true -DskipTests package exec:exec@native
    

    これにより、target/ディレクトリにプラットフォーム用のネイティブ実行ファイルh2exampleが生成されます。

  5. ネイティブ実行ファイルからアプリケーションを実行します。
     ./target/h2example 
    
  6. (オプション)プロジェクトをクリーンアップするには、mvn cleanを実行し、その内容を含むMETA-INF/ディレクトリを削除します。

概要

このガイドでは、GraalVM到達可能性メタデータリポジトリとトレーシングエージェントを使用してネイティブ実行ファイルをビルドする方法を示しました。目的は、その違いを示し、到達可能性メタデータを使用することで作業を簡素化できることを証明することです。

アプリケーションが実行時に動的な機能を呼び出さない場合は、GraalVM到達可能性メタデータリポジトリを有効にする必要がないことに注意してください。その場合のワークフローは次のようになります。

mvn package -Pnative

お問い合わせ