返回

使用原生映像 Maven 插件包含可达性元数据

您可以使用 **Maven** 从 Java 应用程序构建原生可执行文件。为此,请使用作为 原生构建工具项目 一部分提供的 GraalVM 原生映像 Maven 插件。

一个“现实世界”的 Java 应用程序可能需要一些 Java 反射对象,或者它调用一些原生代码,或者访问类路径上的资源 - 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 {
             // 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 添加对 H2 数据库 的依赖,H2 数据库是一个面向 Java 的开源 SQL 数据库。该应用程序通过 JDBC 驱动程序与该数据库进行交互。

    2 在一个 Maven 配置文件中启用 原生映像 Maven 插件,该配置文件附加到 package 阶段。(您将使用 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. 打开 pom.xml,并将以下内容包含在 native 配置文件的 <configuration> 元素中以启用 GraalVM 可达性元数据存储库
     <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 提供元数据配置的第二种方法是在编译时注入 跟踪代理(以下简称“代理”)。该代理默认情况下处于禁用状态,但可以在您的 pom.xml 文件中或通过命令行启用。

该代理可以以三种模式运行

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

请参见下面如何使用跟踪代理收集元数据,并构建一个应用了提供配置的原生可执行文件。

  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. 使用启用的代理执行应用程序涉及更多操作,并且要求您配置一个单独的 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 -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

与我们联系