◀返回
使用 Native Image Maven 插件包含可达性元数据
您可以使用 Maven 从 Java 应用程序构建原生可执行文件。为此,请使用作为 Native Build Tools 项目一部分提供的 GraalVM Native Image Maven 插件。
一个“真实世界”的 Java 应用程序可能需要一些 Java 反射对象,或者它调用一些原生代码,或者访问类路径上的资源——这些是 native-image
工具在构建时必须了解的动态特性,并以元数据的形式提供。(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 { 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; } }
- 打开项目配置文件 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
选项用于启用快速构建模式(仅在开发期间推荐)作为示例。从插件文档中了解其他配置选项。 - (可选)构建应用程序
mvn -DskipTests clean package
这将生成一个可执行的 JAR 文件。
使用 GraalVM 可达性元数据仓库构建原生可执行文件
GraalVM 可达性元数据仓库为默认不支持 GraalVM Native Image 的库提供了 GraalVM 配置。其中之一就是此应用程序依赖的H2 Database。
Native Image Maven 插件在构建时自动从仓库下载元数据。
- 构建原生镜像
mvn -DskipTests -Pnative package
这会在 target/ 目录中生成一个名为
h2example
的平台可执行文件。请注意,元数据被拉取到了新的 target/graalvm-reachability-metadata 目录中。 - 从原生可执行文件运行应用程序,该文件应返回存储在 H2 Database 中的客户列表
./target/h2example
- 在继续之前,运行
mvn clean
清理项目并删除元数据目录及其内容。
使用追踪代理构建原生可执行文件
为 native-image
提供元数据配置的第二种方法是在编译时注入追踪代理(后文简称代理)。代理默认禁用,但可以在 pom.xml 文件中或通过命令行启用。
代理可以在三种模式下运行
- 标准模式:无条件收集元数据。如果您正在构建原生可执行文件,建议使用此模式。(默认)
- 条件模式:有条件地收集元数据。如果您正在为将来使用的原生共享库创建条件元数据,建议使用此模式。
- 直接模式:仅适用于高级用户。此模式允许直接控制传递给代理的命令行。
请参阅下文,了解如何使用追踪代理收集元数据,以及如何应用所提供的配置构建原生可执行文件。在继续之前,请清理上次构建的项目:mvn clean
。
- 通过将以下内容添加到
native
配置文件的<configuration>
元素中来启用代理<agent> <enabled>true</enabled> </agent>
配置块应类似于此
<configuration> <agent> <enabled>true</enabled> </agent> <buildArgs> <buildArg>-Ob</buildArg> </buildArgs> </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 -DskipTests -DskipNativeBuild=true package exec:exec@java-agent
代理会捕获并记录对 H2 Database 的调用以及在测试运行期间遇到的所有动态特性,并将其写入 target/native/agent-output/main/ 目录中的 reachability-metadata.json 文件。
- 使用代理收集的配置构建原生可执行文件
mvn -Pnative -DskipTests package
它会在 target/ 目录中生成一个名为 h2example 的平台原生可执行文件。
- 从原生可执行文件运行应用程序
./target/h2example
总结
本指南演示了如何使用 GraalVM 可达性元数据仓库和追踪代理来构建原生可执行文件。目标是展示两者之间的差异,并证明使用可达性元数据如何简化工作。使用 GraalVM 可达性元数据仓库增强了 Native Image 对依赖第三方库的 Java 应用程序的可用性。