毫无疑问,模块系统(JPMS)是 Java 9 中最显著的新特性。但 JDK 还有很多其他有用的功能,尚未得到广泛讨论。
其中之一是新的 ToolProvider
SPI,它定义了一种统一的方式来以程序化的方式调用 JDK 中的所有工具(例如 jar
、jlink
等)。例如,想象一下,您想在您的 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"
);
这里发生了什么?ToolProvider
的 findFirst()
方法用于获取名为 "jar" 的工具实例。同样,你也可以获取 "jlink"、"jmod" 或任何其他 JDK 工具的实例。只需确保使用 java.util.spi.ToolProvider
而不是 javax.tools.ToolProvider
,后者从 Java 6 开始就已经存在,并且仅用于获取 Java 编译器的实例。
一旦你获得了工具的引用,就可以通过传递以下内容来执行它:
-
接收启动工具的标准输出和错误输出的
PrintStream
(或PrintWriter
)实例;在示例中,我们使用System.out
和System.err
将任何输出传播到当前的标准输出和错误流,但当然你也可以将流重定向到文件,或者只捕获到内存中等。 -
工具的参数,就像你在 CLI 中传递的那样;在示例中,我们传递了
--describe-module
和--file
选项以及我们想要显示模块元数据的文件路径
这就是使用 Java 9 以编程方式运行任何 JDK 工具所需的所有内容,使用一个简单的统一 API,而不需要启动一个单独的进程。特别是 Maven 或 Gradle 等基于 JDK 的工具的实现者将非常受益于这个新 API。