ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

使用 Native Image Gradle Plugin 集成 Reachability Metadata:从元数据仓库到 Tracing Agent 的完整实战指南

使用 Native Image Gradle Plugin 集成 Reachability Metadata:从元数据仓库到 Tracing Agent 的完整实战指南 使用 Native Image Gradle Plugin 集成 Reachability Metadata从元数据仓库到 Tracing Agent 的完整实战指南【免费下载链接】graalGraalVM compiles applications into native executables that start instantly, scale fast, and use fewer compute resources 项目地址: https://gitcode.com/gh_mirrors/gr/graal本指南基于 GraalVM 仓库的官方文档编写讲解如何在 Gradle 构建中通过GraalVM Native Image Gradle Plugin为 Java 应用提供可达性元数据Reachability Metadata并最终构建出可运行的原生可执行文件。文中将以一个依赖 H2 数据库的 JDBC 应用为例完整演示使用 GraalVM Reachability Metadata Repository 自动下载元数据与使用 Tracing Agent 采集元数据两条主流路径帮助你理解二者差异并掌握元数据在构建期被解析、加载与校验的底层机制。为什么 Java 应用需要 Reachability MetadataJava 虚拟机的动态语言特性如反射、动态代理、JNI、类路径资源访问在运行时才会计算出被动态访问的程序元素字段、方法、类、资源 URL 等。在 HotSpot 上这没有问题因为所有类文件和资源在运行时都可用、可加载但代价是额外的内存与启动时间开销。为了让原生二进制足够小、启动足够快native-image构建器在构建期执行静态分析封闭世界假设只保留应用正确性所需的程序元素。然而静态分析无法推断出那些仅在运行时才可知的动态访问因此native-image要求开发者提供可达性元数据Reachability Metadata以声明这些动态访问的元素必须被包含进镜像。正如官方文档 Reachability Metadata 所述为构建器提供正确且完备的可达性元数据能保证应用正确性并确保第三方库在运行时的兼容性。在仓库源码中构建器通过ReachabilityMetadataResources选项定义于 ConfigurationFiles.java加载这些资源并支持通过--strict-configuration对应源码中的StrictConfiguration选项在配置文件不符合 schema 时直接中止构建而不是仅给出警告。元数据的几种提供方式你可以通过以下方式向native-image构建器提供可达性元数据详见 Reachability Metadata在代码中计算元数据给动态访问 API 传入常量参数例如Class.forName(Foo)构建期即可求值并存入原生二进制的初始堆或在构建期初始化类并把动态元素存入初始堆。通过 JSON 文件指定在类路径下的META-INF/native-image/groupId/artifactId/目录放置一个或多个reachability-metadata.json文件。使用-H:Preserveclasspath-selector参数直接保留指定的包、模块或类。使用 Feature API用于需要扫描类路径来计算正确元数据的高级场景。而在Gradle 构建中通常只需要掌握下面三种注入元数据的途径方式适用场景操作要点GraalVM Reachability Metadata Repository依赖了尚不支持 Native Image 的第三方库如 H2构建期插件自动下载元数据零配置Tracing Agent跟踪代理应用自身使用了反射、JNI、资源等动态特性在 JVM 上跑一遍应用agent 自动生成 JSON 配置资源自动探测Autodetecting所需资源直接位于类路径、src/main/resources/目录下插件自动识别无需额外操作本指南将重点演示前两种方式并用同一个 H2 JDBC 应用对比它们的差异。准备演示应用 H2Example为了体现真实世界应用的典型动态特性示例应用使用H2 数据库通过 JDBC 驱动访问这类场景恰好需要反射与资源类元数据。注意执行 Gradle 需要 Java 1721参见 Gradle 兼容性矩阵。如果你想用 Java 23或更高版本运行应用有一个变通方案将JAVA_HOME指向 1721 的 JDK将GRAALVM_HOME指向 GraalVM for JDK 23。详细说明见 Native Image Gradle Plugin 官方文档中Installing GraalVM Native Image tool一节。前置条件安装 GraalVM JDK。最简单的方式是通过 SDKMAN! 安装也可以从 GraalVM 官网下载。确保GRAALVM_HOME已正确设置。第一步创建项目骨架在 IDE 中新建一个名为H2Example的 Gradle Java 项目包名为org.graalvm.example。将默认的app/目录重命名为H2Example/。将默认的App.java重命名为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(); }); SetString 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(); SetString 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(SetString customers) { ListString 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; } }这段代码通过DriverManager.getConnection()动态建立 JDBC 连接内部会触发驱动类的反射加载——这正是原生镜像构建中最典型的元数据需求场景。第二步配置 build.gradle打开build.gradle替换为以下内容plugins { id application // 1. Native Image Gradle plugin id org.graalvm.buildtools.native version 0.10.3 } repositories { mavenCentral() } // 2. Application main class application { mainClass.set(org.graalvm.example.H2Example) } dependencies { // 3. H2 Database dependency implementation(com.h2database:h2:2.2.220) } // 4. Native Image build configuration graalvmNative { binaries { main { imageName.set(h2example) buildArgs.add(-Ob) } } }配置要点逐条说明启用 Native Image Gradle 插件版本号0.10.3。插件会自行发现需要传给native-image的 JAR 文件以及可执行文件的主类。显式指定应用主类避免插件误判。添加 H2 数据库依赖应用通过其 JDBC 驱动与数据库交互。H2 是一个开源 SQL 数据库也是默认不支持 Native Image的典型第三方库因此非常适合用来演示元数据仓库的价值。向native-image传递参数graalvmNative块中的buildArgs与命令行传参完全一致示例中的-Ob用于开启快速构建模式仅推荐开发期使用imageName.set(h2example)指定最终二进制的名字。更多配置项见插件官方文档的 Configuration 章节。第三步配置 settings.gradleNative Image Gradle 插件尚未发布到 Gradle Plugin Portal因此需要额外声明插件仓库。打开settings.gradle替换为pluginManagement { repositories { mavenCentral() gradlePluginPortal() } } rootProject.name H2Example include(H2Example)注意pluginManagement {}块必须出现在文件中任何其他语句之前。第四步可选先跑通 JVM 版本gradle run这会生成一个可运行的 JAR 并打印出数据库中存储的客户列表作为后续原生构建的基准行为。方式一使用 GraalVM Reachability Metadata RepositoryGraalVM Reachability Metadata Repository 为那些默认不支持 GraalVM Native Image的库提供 GraalVM 配置本应用的依赖H2 Database正是其中之一。使用此方式时Native Image Gradle 插件会在构建期自动从该仓库下载对应的元数据无需任何手工配置。用一条命令即可完成构建原生可执行文件 运行gradle nativeRun生成的原生可执行文件名为h2example位于build/native/nativeCompile目录该命令会直接从原生可执行文件运行应用输出与gradle run一致的客户列表。这一方式的本质是插件把从元数据仓库下载的reachability-metadata.json以META-INF/native-image/groupId/artifactId/的标准目录结构放入类路径使native-image构建器在构建期自动发现并加载对应源码中 ConfigurationFiles.java 所管理的ReachabilityMetadataResources资源机制。这正是文档 Reachability Metadata 所描述的 JSON 元数据规范在 Gradle 构建链中的落地。方式二使用 Tracing Agent第二种方式是在编译期注入 Tracing Agent跟踪代理让它在 JVM 运行应用时自动采集动态特性调用并生成 JSON 配置文件。理解 Agent 的三种运行模式模式行为推荐场景Standard标准无条件采集元数据构建原生可执行文件本指南采用Conditional条件带条件采集元数据基于typeReached为供进一步使用的原生共享库创建条件元数据Direct直接高级用户专用可直接控制传给 agent 的命令行高级/调试场景第一步配置 agent打开build.gradle在graalvmNative块中加入agent { defaultMode standard }defaultMode定义了 agent 的运行模式。如果你更习惯命令行传参等效写法是-Pagentstandard。第二步在 JVM 上带 agent 运行应用Native Image Gradle 插件约定向任何继承JavaForkOptions的 Gradle 任务例如test、run传递-Pagent即可启用 agentgradle -Pagent runagent 会捕获并记录本次运行中 H2 数据库调用及所有遇到的动态特性将结果写入build/native/agent-output/run目录下的 JSON 文件中。关于 agent 的采集机制Collecting Metadata Automatically 给出了底层细节agent 使用 JVM Tool InterfaceJVMTI跟踪动态特性访问在 JVM 退出时将元数据写成 JSON如果希望覆盖更多执行路径可以多次运行应用并用config-merge-dir选项把多次结果合并到既有配置集合中还可以用config-write-period-secs与config-write-initial-delay-secs让 agent 周期性而非仅退出时写出元数据。此外由于 agent 只观察已执行到的代码官方建议手动审查生成的配置文件并尽量让应用输入覆盖尽可能多的代码路径。第三步将元数据拷贝进项目使用插件提供的metadataCopy任务把采集到的元数据复制到项目的META-INF/native-image/目录gradle metadataCopy --task run --dir src/main/resources/META-INF/native-image输出目录虽然不是强制的但推荐使用src/main/resources/META-INF/native-image/因为native-image工具会自动从该位置读取元数据。关于如何为应用自动采集元数据的更多细节参见 Collecting Metadata Automatically。第四步基于 agent 采集的配置构建原生可执行文件gradle nativeRun该命令同样会先构建再直接运行原生可执行文件。第五步可选清理项目gradle clean并删除META-INF目录及其内容。深入理解agent 采集的元数据长什么样无论元数据来自仓库还是 agent最终都以 JSON 形式存在。下面结合 Reachability Metadata 的格式参考帮助你读懂 agent 生成的配置。一个reachability-metadata.json文件的顶层是包含若干数组的对象例如{ reflection: [], resources: [] }以反射为例一个类型条目可以包含以下常用字段字段类型说明typeString/Object类名、代理定义或 lambda 定义allDeclaredConstructorsBoolean启用对所有已声明构造器的访问allPublicConstructorsBoolean启用对所有公有构造器的访问allDeclaredMethodsBoolean启用对所有已声明方法的访问allPublicMethodsBoolean启用对所有公有方法含继承的访问allDeclaredFieldsBoolean启用对所有已声明字段的访问allPublicFieldsBoolean启用对所有公有字段含继承的访问methodsArray精确指定需要访问的方法nameparameterTypesfieldsArray精确指定需要访问的字段nameunsafeAllocatedBoolean允许通过Unsafe.allocateInstance()进行不安全分配此外reflection、jni、resources各节都支持condition字段typeReached用于条件注册只有当指定全限定类在运行时被触达时对应条目才生效。条件元数据可以避免原生二进制无谓膨胀是库维护者推荐采用的形式。关于元数据校验官方文档建议使用reachability-metadata-schema-v1.2.0.json位于仓库docs/reference-manual/native-image/assets/目录来校验配置若配置不符合 schema可通过--strict-configuration让构建直接失败而不是仅告警对应 ConfigurationFiles.java 中的StrictConfiguration选项。进阶agent 的过滤与注入技巧当自动采集的元数据包含过多无关内容时Collecting Metadata Automatically 提供了两类过滤机制Caller-based Filters调用方过滤按谁发起的访问过滤。内置过滤规则会排除源自 JVM 或 Native Image 直接支持的 Java 类库如java.nio的访问也可通过caller-filter-file提供自定义规则文件规则支持includeClasses/excludeClasses并可用.*仅当前包与.**当前包及所有子包通配。Access Filters访问目标过滤按访问的目标过滤例如排除com.example.internal.**包下所有类的元数据。两类过滤器可以组合使用。如果 Java 进程由应用或脚本启动、难以改动java命令行还可以通过JAVA_TOOL_OPTIONS环境变量注入 agent配合config-output-dir路径中的{pid}与{datetime}占位符避免多个并发进程互相覆盖输出。对于非类路径上的配置目录可用-H:ConfigurationFileDirectories直接指定类路径上但不在META-INF/native-image/下的目录则用-H:ConfigurationResourceRoots指定。总结本指南通过一个真实的 H2 JDBC 应用完整演示了在 Gradle 构建中集成 Reachability Metadata 的两种主流方式GraalVM Reachability Metadata Repository插件在构建期自动下载第三方库如 H2的元数据一条gradle nativeRun即可产出并运行原生可执行文件几乎零配置。它显著提升了 Native Image 对依赖第三方库的 Java 应用的可用性。Tracing Agent在 JVM 上运行应用时自动采集动态特性访问并生成 JSON 配置适合应用自身使用了反射、资源、JNI 等动态特性的场景配合metadataCopy任务可将元数据固化到项目源码中。两种方式的差异在于前者由社区维护的元数据仓库统一提供第三方库的配置省时省力后者采集的是你自己应用的精确行为覆盖面取决于运行时执行的代码路径因此建议多路径运行并人工复核生成的配置。结合 Reachability Metadata 与 Collecting Metadata Automatically 两篇参考文档以及仓库中 ConfigurationFiles.java 的实现可以进一步了解元数据的 JSON schema、条件注册语义与构建期加载机制为真实项目落地 Native Image 提供坚实依据。相关文档Reachability Metadata元数据类型、JSON 格式参考与条件注册语义Collecting Metadata AutomaticallyTracing Agent 的采集、过滤与合并机制Native Image Basics静态分析、封闭世界假设与构建期/运行期概念Build a Native Executable with Reflection反射场景的元数据入门指南【免费下载链接】graalGraalVM compiles applications into native executables that start instantly, scale fast, and use fewer compute resources 项目地址: https://gitcode.com/gh_mirrors/gr/graal创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表