返回

使用 Native Image Maven 插件包含可达性元数据

您可以使用 Maven 从 Java 应用程序构建原生可执行文件。为此,请使用作为 Native Build Tools 项目一部分提供的 GraalVM Native Image Maven 插件。

一个“真实世界”的 Java 应用程序可能需要一些 Java 反射对象,或者它调用一些原生代码,或者访问类路径上的资源——这些是 native-image 工具在构建时必须了解的动态特性,并以元数据的形式提供。(Native Image 在构建时动态加载类,而不是在运行时。)

根据您的应用程序依赖项,可以通过三种方式提供元数据

  1. 使用 GraalVM 可达性元数据仓库
  2. 使用追踪代理
  3. 自动检测 (如果所需资源直接在类路径上,位于 src/main/resources/ 目录中)

本指南演示了如何使用 GraalVM 可达性元数据仓库追踪代理构建原生可执行文件。本指南的目标是说明这两种方法之间的区别,并演示如何使用可达性元数据来简化您的开发任务。

我们建议您按照说明一步一步地创建应用程序。或者,您可以直接查看完整示例

准备一个演示应用程序

前提条件

请确保您已安装 GraalVM JDK。最简单的入门方法是使用 SDKMAN!。有关其他安装选项,请访问下载部分

  1. 在您喜欢的 IDE 或从命令行使用 Maven 创建一个名为“H2Example”的新 Java 项目,位于 org.graalvm.example 包中。

  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 {
             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);
    
             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<>();
             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. 打开项目配置文件 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>
             <h2.version>2.2.220</h2.version>
             <maven.compiler.source>21</maven.compiler.source>
             <maven.compiler.target>21</maven.compiler.target>
             <native.maven.plugin.version>0.10.3</native.maven.plugin.version>
             <mainClass>org.graalvm.example.H2Example</mainClass>
             <imageName>h2example</imageName>
         </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.3</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-assembly-plugin</artifactId>
                     <version>3.7.0</version>
                     <configuration>
                         <descriptorRefs>
                             <descriptorRef>jar-with-dependencies</descriptorRef>
                         </descriptorRefs>
                         <archive>
                             <manifest>
                                 <mainClass>${mainClass}</mainClass>
                             </manifest>
                         </archive>
                     </configuration>
                     <executions>
                         <execution>
                             <id>assemble-all</id>
                             <phase>package</phase>
                             <goals>
                                 <goal>single</goal>
                             </goals>
                         </execution>
                     </executions>
                 </plugin>
             </plugins>
         </build>
     </project>
    

    1 添加对 H2 Database 的依赖,H2 Database 是一个用于 Java 的开源 SQL 数据库。应用程序通过 JDBC 驱动程序与此数据库交互。

    2 在 Maven 配置文件中启用Native Image Maven 插件,并将其绑定到 package 阶段。您将使用 Maven 配置文件构建原生可执行文件。Maven 配置文件允许您决定是仅构建 JAR 文件,还是构建原生可执行文件。该插件会发现需要传递给 native-image 的 JAR 文件以及可执行的主类应该是什么。

    3 您可以使用 <buildArgs> 部分将参数传递给底层的 native-image 构建工具。在单独的 <buildArg> 标签中,您可以像在命令行上一样传递参数。-Ob 选项用于启用快速构建模式(仅在开发期间推荐)作为示例。从插件文档中了解其他配置选项。

  4. (可选)构建应用程序
     mvn -DskipTests clean package
    

    这将生成一个可执行的 JAR 文件。

使用 GraalVM 可达性元数据仓库构建原生可执行文件

GraalVM 可达性元数据仓库为默认不支持 GraalVM Native Image 的库提供了 GraalVM 配置。其中之一就是此应用程序依赖的H2 Database

Native Image Maven 插件在构建时自动从仓库下载元数据

  1. 构建原生镜像
     mvn -DskipTests -Pnative package
    

    这会在 target/ 目录中生成一个名为 h2example 的平台可执行文件。请注意,元数据被拉取到了新的 target/graalvm-reachability-metadata 目录中。

  2. 从原生可执行文件运行应用程序,该文件应返回存储在 H2 Database 中的客户列表
     ./target/h2example 
    
  3. 在继续之前,运行 mvn clean 清理项目并删除元数据目录及其内容。

使用追踪代理构建原生可执行文件

native-image 提供元数据配置的第二种方法是在编译时注入追踪代理(后文简称代理)。代理默认禁用,但可以在 pom.xml 文件中或通过命令行启用。

代理可以在三种模式下运行

  • 标准模式:无条件收集元数据。如果您正在构建原生可执行文件,建议使用此模式。(默认)
  • 条件模式:有条件地收集元数据。如果您正在为将来使用的原生共享库创建条件元数据,建议使用此模式。
  • 直接模式:仅适用于高级用户。此模式允许直接控制传递给代理的命令行。

请参阅下文,了解如何使用追踪代理收集元数据,以及如何应用所提供的配置构建原生可执行文件。在继续之前,请清理上次构建的项目:mvn clean

  1. 通过将以下内容添加到 native 配置文件的 <configuration> 元素中来启用代理
     <agent>
         <enabled>true</enabled>
     </agent>
    

    配置块应类似于此

     <configuration>
         <agent>
             <enabled>true</enabled>
         </agent>
         <buildArgs>
             <buildArg>-Ob</buildArg>
         </buildArgs>
     </configuration>
    
  2. 使用代理执行应用程序更为复杂,需要您配置一个单独的 MOJO 执行,以允许分叉 Java 进程。在 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 -DskipTests -DskipNativeBuild=true package exec:exec@java-agent
    

    代理会捕获并记录对 H2 Database 的调用以及在测试运行期间遇到的所有动态特性,并将其写入 target/native/agent-output/main/ 目录中的 reachability-metadata.json 文件。

  4. 使用代理收集的配置构建原生可执行文件
     mvn -Pnative -DskipTests package
    

    它会在 target/ 目录中生成一个名为 h2example 的平台原生可执行文件。

  5. 从原生可执行文件运行应用程序
     ./target/h2example
    

总结

本指南演示了如何使用 GraalVM 可达性元数据仓库和追踪代理来构建原生可执行文件。目标是展示两者之间的差异,并证明使用可达性元数据如何简化工作。使用 GraalVM 可达性元数据仓库增强了 Native Image 对依赖第三方库的 Java 应用程序的可用性。

联系我们