◀返回
使用原生映像 Maven 插件包含可达性元数据
您可以使用 **Maven** 从 Java 应用程序构建原生可执行文件。为此,请使用作为 原生构建工具项目 一部分提供的 GraalVM 原生映像 Maven 插件。
一个“现实世界”的 Java 应用程序可能需要一些 Java 反射对象,或者它调用一些原生代码,或者访问类路径上的资源 - native-image
工具在构建时必须意识到的动态功能,并以 元数据 的形式提供。(原生映像在构建时动态加载类,而不是在运行时。)
根据您的应用程序依赖项,有三种方法可以提供元数据
- 使用 GraalVM 可达性元数据存储库
- 使用跟踪代理
- 自动检测(如果所需资源直接在类路径上可用,位于 src/main/resources/ 目录中)
本指南演示了如何使用 GraalVM 可达性元数据存储库 以及 跟踪代理 构建原生可执行文件。本指南的目的是说明这两种方法之间的区别,并演示如何使用可达性元数据可以简化您的开发任务。
我们建议您按照说明逐步创建应用程序。或者,您可以直接转到 已完成的示例。
准备演示应用程序
先决条件
确保您已安装 GraalVM JDK。最简单的入门方法是使用 SDKMAN!。有关其他安装选项,请访问 下载部分。
-
在您喜欢的 IDE 或命令行中使用 **Maven** 创建一个名为“H2Example”的新 Java 项目,位于
org.graalvm.example
包中。 - 打开主类文件 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; } }
-
删除 H2Example/src/test/java/ 目录(如果存在)。
- 打开项目配置文件 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
选项(仅在开发期间推荐)用作示例。从 插件文档 中了解其他配置选项。 - (可选)构建应用程序。从存储库的根目录运行以下命令
mvn clean package
这会生成一个“可执行”JAR 文件,其中包含应用程序的所有依赖项,还包含一个正确配置的 MANIFEST 文件。
使用 GraalVM 可达性元数据存储库构建原生可执行文件
原生映像 Maven 插件提供了对 GraalVM 可达性元数据存储库 的支持。此存储库为默认情况下不支持 GraalVM 原生映像的库提供了 GraalVM 配置。其中之一是本应用程序所依赖的 H2 数据库。需要显式启用该支持。
- 打开 pom.xml,并将以下内容包含在
native
配置文件的<configuration>
元素中以启用 GraalVM 可达性元数据存储库<metadataRepository> <enabled>true</enabled> </metadataRepository>
配置块应类似于此
<configuration> <buildArgs> <buildArg>-Ob</buildArg> </buildArgs> <metadataRepository> <enabled>true</enabled> </metadataRepository> </configuration>
该插件会自动从存储库下载元数据。
- 现在使用配置文件构建原生可执行文件(请注意,配置文件名用
-P
标记指定)mvn package -Pnative
这会为平台在 target/ 目录中生成一个名为
h2example
的原生可执行文件。 -
从原生可执行文件运行应用程序
./target/h2example
应用程序返回存储在 H2 数据库中的客户列表。
使用跟踪代理构建原生可执行文件
为 native-image
提供元数据配置的第二种方法是在编译时注入 跟踪代理(以下简称“代理”)。该代理默认情况下处于禁用状态,但可以在您的 pom.xml 文件中或通过命令行启用。
该代理可以以三种模式运行
- 标准模式:无条件收集元数据。如果您正在构建原生可执行文件,建议使用此模式。(默认)
- 条件模式:有条件地收集元数据。如果您要为将用于进一步使用的原生共享库创建条件元数据,建议使用此模式。
- 直接模式:仅供高级用户使用。此模式允许直接控制传递给代理的命令行。
请参见下面如何使用跟踪代理收集元数据,并构建一个应用了提供配置的原生可执行文件。
- 通过将以下内容添加到
native
配置文件的<configuration>
元素中来启用代理<agent> <enabled>true</enabled> </agent>
配置块应类似于此
<configuration> <agent> <enabled>true</enabled> </agent> <buildArgs> <buildArg>-Ob</buildArg> </buildArgs> <metadataRepository> <enabled>true</enabled> </metadataRepository> </configuration>
- 使用启用的代理执行应用程序涉及更多操作,并且要求您配置一个单独的 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>
- 在 JVM 上使用启用的代理运行您的应用程序
mvn -Pnative -Dagent=true -DskipTests -DskipNativeBuild=true package exec:exec@java-agent
该代理会捕获并记录对 H2 数据库的调用以及测试运行期间遇到的所有动态功能,并将它们记录到 target/native/agent-output/main/ 目录中的多个 *-config.json 文件中。
- 使用代理收集的配置构建原生可执行文件
mvn -Pnative -Dagent=true -DskipTests package exec:exec@native
这会在 target/ 目录中为平台生成一个名为 h2example 的原生可执行文件。
- 从原生可执行文件运行应用程序
./target/h2example
- (可选)要清理项目,请运行
mvn clean
,并删除 META-INF/ 目录及其内容。
总结
本指南演示了如何使用 GraalVM 可达性元数据存储库 以及跟踪代理构建原生可执行文件。目标是展示它们之间的区别,并证明使用可达性元数据可以简化工作。
请注意,如果您的应用程序在运行时不调用任何动态功能,则启用 GraalVM 可达性元数据存储库是没有必要的。在这种情况下,您的工作流程将是
mvn package -Pnative