Java 9 中程序化调用 JDK 工具

发布者    |       讨论

毫无疑问,模块系统(JPMS)是 Java 9 中最显著的新特性。但 JDK 还有很多其他有用的功能,尚未得到广泛讨论。

其中之一是新的 ToolProvider SPI,它定义了一种统一的方式来以程序化的方式调用 JDK 中的所有工具(例如 jarjlink 等)。例如,想象一下,您想在您的 Java 应用程序内部创建一个 JAR 存档。在 Java 9 之前,您有两个选择来实现这一点

  • 启动一个单独的进程来运行 jar 二进制文件

  • 找出 jar 命令内部使用的类,并直接调用它们

这两个选项都不理想。前者涉及到为创建操作系统级进程而带来的开销,并且需要一些编码来定位 Java 家目录中要执行的二进制文件,并正确捕获分叉进程的任何输出。第二个选项没有分叉新进程的缺点,但它需要了解要执行的工具的实现。因此,例如,在 jar 命令的情况下,您必须知道 java.util.jar.JarOutputStream 用于生成 JAR 文件。

Java 9 的 ToolProvider SPI

那么,如何使用 Java 9 来解决这个问题呢?让我们来看看 java.util.spi.ToolProvider,这是一个新的 SPI,它允许您以统一的方式在当前进程中以程序化的方式运行任何 JDK 工具。也就是说,没有进程分叉的开销,也不需要了解工具的内部工作原理,因为它们通过一个通用接口执行。

作为一个例子,让我们看看它是如何用来运行 jar 实用程序,并使用其新的 --describe-module 选项来显示给定模块化 JAR 的模块元数据。

Optional<ToolProvider> jar = ToolProvider.findFirst( "jar" );
jar.get().run(
        System.out,
        System.err,
        "--describe-module",
        "--file",
        "path/to/some/module.jar"
);

这里发生了什么?ToolProviderfindFirst() 方法用于获取名为 "jar" 的工具实例。同样,你也可以获取 "jlink"、"jmod" 或任何其他 JDK 工具的实例。只需确保使用 java.util.spi.ToolProvider 而不是 javax.tools.ToolProvider,后者从 Java 6 开始就已经存在,并且仅用于获取 Java 编译器的实例。

一旦你获得了工具的引用,就可以通过传递以下内容来执行它:

  • 接收启动工具的标准输出和错误输出的 PrintStream(或 PrintWriter)实例;在示例中,我们使用 System.outSystem.err 将任何输出传播到当前的标准输出和错误流,但当然你也可以将流重定向到文件,或者只捕获到内存中等。

  • 工具的参数,就像你在 CLI 中传递的那样;在示例中,我们传递了 --describe-module--file 选项以及我们想要显示模块元数据的文件路径

这就是使用 Java 9 以编程方式运行任何 JDK 工具所需的所有内容,使用一个简单的统一 API,而不需要启动一个单独的进程。特别是 Maven 或 Gradle 等基于 JDK 的工具的实现者将非常受益于这个新 API。


返回顶部