在 Java 程序中运行 JAR

1、简介

在开发 Java 项目时,我们可能会遇到这样的情况:需要在 Java 程序中启动一个单独的进程运行外部 JAR(可执行 JAR)并查看输出,或者可能想要执行外部 JAR 中带有 main 方法的类文件。

2、运行可执行 JAR

可执行 JAR 是一种 JAR 文件类型,它包含一个设置了 Main-Class 属性的清单(manifest)文件。该属性指向应首先运行(使用 main 方法)的类文件。

我们可以使用 java -jar <example.jar> 命令从命令行运行该 JAR。也可以在 Java 程序中使用 ProcessBuilder 来实现类似的结果。

下面的代码演示了如何以编程式将可执行 JAR 作为单独进程运行,并在控制台中查看输出结果:

@Test
public void givenRunnableJar_whenExecuted_thenShouldRunSuccessfully() {
    Process process = null;
    try {
        String jarFile = new File(Objects.requireNonNull(getClass().getClassLoader()
          .getResource(RUNNABLE_JAR_PATH))
          .toURI()).getAbsolutePath();

        ProcessBuilder processBuilder = new ProcessBuilder("java", "-jar", jarFile);
        processBuilder.redirectErrorStream(true);

        process = processBuilder.start();
        try (InputStream inputStream = process.getInputStream()) {
            byte[] output = inputStream.readAllBytes();
            System.out.println("Output: " + new String(output));
        }

        int exitCode = process.waitFor();
        Assert.assertEquals("Process exited with an unexpected exit code", 0, exitCode);
    } catch (IOException | InterruptedException | URISyntaxException e) {
        Assert.fail("Test failed due to exception: " + e.getMessage());
    } finally {
        if (process != null) {
            process.destroy();
        }
    }
}

首先,通过提供 JAR 文件(本例中为可执行 JAR)的绝对路径来创建文件对象。getClass().getClassLoader().getResource(RUNNABLE_JAR_PATH) 方法会获取资源 URL,要确保 URL 不为 null,并将其转换为 URI

然后,使用 java -jar 设置一个命令,这是执行可执行 JAR 的标准方法。接着,使用 ProcessBuilder 将 JAR 作为一个新进程执行。

开始运行后,我们会尝试从输入流中捕获程序输出。然后将输出转换成字符串并打印在控制台上。这与运行 java -jar 命令时看到的情况类似。

最后,使用 process.waitFor() 等待进程完成。如果退出代码(exit code)为零,则表示执行成功。如果退出代码为非零值,则表示在执行过程中发生了错误,测试将失败。

3、运行非可执行 JAR 文件

不可执行的 JAR 文件在其清单(manifest)文件中没有 Main-Class 属性。我们必须明确指定声明了 main 方法的类。

下面的代码演示了如何以编程式将不可执行 JAR 中的类文件作为单独进程执行,并在控制台中查看输出结果:

@Test
public void givenNonRunnableJar_whenExecutedWithMainClass_thenShouldRunSuccessfully() {
    Process process = null;
    try {
        String jarFile = new File(Objects.requireNonNull(getClass().getClassLoader()
          .getResource(NON_RUNNABLE_JAR_PATH))
          .toURI()).getAbsolutePath();

        String[] command = { "java", "-cp", jarFile, "com.company.HelloWorld", "arg1", "arg2" };
        ProcessBuilder processBuilder = new ProcessBuilder(command);
        processBuilder.redirectErrorStream(true);

        process = processBuilder.start();  // 开始进程
        try (InputStream inputStream = process.getInputStream()) {
            byte[] output = inputStream.readAllBytes();
            System.out.println("Output: " + new String(output));
        }

        int exitCode = process.waitFor();
        Assert.assertEquals("Process exited with an unexpected exit code", 0, exitCode);
    } catch (IOException | InterruptedException | URISyntaxException e) {
        Assert.fail("Test failed due to exception: " + e.getMessage());
    } finally {
        if (process != null) {
            process.destroy();  // 资源清理
        }
    }
}

首先,通过提供 JAR 文件(本例中为不可执行 JAR)的绝对路径来创建 process.waitForobject

然后,使用 ProcessBuilder 设置执行环境。redirectErrorStream(true) 方法将错误流与标准输出流合并,这样我们就能在一个地方捕获所有输出。

然后通过 processBuilder.start() 将该命令作为一个新进程执行。我们从进程的输入流中捕获程序的输出,然后将其转换为字符串并打印到控制台上。输出结果与手动运行命令时的输出结果类似。

最后,通过 process.waitFor() 阻塞当前进程,直至任务完成。

如果退出代码为零,则表示 JAR 已成功执行;如果退出代码为非零值,则表示在执行 JAR 时出现了错误。

4、总结

本文介绍了如何在 Java 程序中使用 ProcessBuilder 来直接执行 JAR 文件(包括可执行 JAR 和非可执行 JAR),以及如何获取子进程的输出。


Ref:https://www.baeldung.com/java-execute-jar-file