如何在 Java 中运行子进程
作者:互联网
本文介绍了如何在 Java 中运行子进程(非 jar)。确切地说,要求从测试程序内部启动一个新进程,而非直接在测试(进程)内部运行。尽管不是什么炫酷的技术,但以前没有做过类似的事情,不清楚如何下手。
经过一番搜索,在 Stack Overflow 中找到了[解答][1]。为了更好地解决问题,重写了答案。
[1]:https://stackoverflow.com/questions/636367/executing-a-java-application-in-a-separate-process
```java
class JavaProcess {
private JavaProcess() {
}
public static int exec(Class clazz, List<String> jvmArgs, List<String> args) throws IOException,
InterruptedException {
String javaHome = System.getProperty("java.home");
String javaBin = javaHome + File.separator + "bin" + File.separator + "java";
String classpath = System.getProperty("java.class.path");
String className = clazz.getName();
List<String> command = new ArrayList<>();
command.add(javaBin);
command.addAll(jvmArgs);
command.add("-cp");
command.add(classpath);
command.add(className);
command.addAll(args);
ProcessBuilder builder = new ProcessBuilder(command);
Process process = builder.inheritIO().start();
process.waitFor();
return process.exitValue();
}
}
```
上面的静态函数接收的参数包括 `Class`、JVM 参数以及 `main` 方法执行参数,通过参数可以完全控制子进程执行过程,例如减小执行时 JVM 堆空间(本文目标之一)。
注意:实际运行时,需要提供 `main` 方法。
为了让主程序与子进程使用同一个 Java 版本,可以使用 `javaBin` 中的路径调用 Java 可执行程序。如果把 `javaBin` 替换为 `java`,会调用本机默认安装的 Java 版本。大多数情况不会有问题,但很可能出现意外。
所有 command 加载到 `command` 列表后,都会传给 `ProcessBuilder` 生成命令。`ProcessBuilder` 对 `command` 中每个参数用空格分隔。也可以调用重载的构造函数,直接手工传入整个命令字符串,无需手工添加字符串参数。
一般在 IO 传递给执行的主进程后会启动子进程。通常期望看到 `stdout` 和 `stderr` 输出。可以直接调用 `inheritIO`,也可以使用以下方法(额外配置了 `stdin` 子进程):
```java
builder
.redirectInput(ProcessBuilder.Redirect.INHERIT)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT);
```
最后,`waitFor` 会通知执行线程等待产生的子进程执行结束,无论执行成功或还是出现错误,只要子进程以某种方式结束就可以了。主程序会继续执行。子进程的结束状态可以通过进程结束时 `exitValue` 判断。例如,通常 0 代表执行成功,1 代表语法无效错误。根据每个应用不同,还有其他 exit code。
调用 `exec` 方法:
```java
JavaProcess.exec(MyProcess.class, List.of("-Xmx200m"), List.of("argument"))
```
> 译注:List.of 是 Java 9 引入的方法,之前的版本可以使用 Arrays.asList。
将执行下面的命令(或类似的命令):
```shell
/Library/Java/JavaVirtualMachines/jdk-12.0.1.jdk/Contents/Home/bin/java -cp /playing-around-for-blogs MyProcess "argument"
```
为了整洁起见,已经移除了包括 classpath 在内的许多路径。实际执行结果可能看起来要长许多,与具体应用相关。上面展示的命令是可运行最短路径(为本文的示例定制)。
`exec` 方法很灵活,非常有助于描述正在进行的工作。不仅如此,还可以返回 `ProcessBuilder` 对象,增强扩展性、适用更多场景。不但可以在多个地方重用代码段,还能灵活配置 IO 重定向,确定子进程在后台运行或阻塞运行。看起来像下面这样:
```java
public static ProcessBuilder exec(Class clazz, List<String> jvmArgs, List<String> args) {
String javaHome = System.getProperty("java.home");
String javaBin = javaHome + File.separator + "bin" + File.separator + "java";
String classpath = System.getProperty("java.class.path");
String className = clazz.getName();
List<String> command = new ArrayList<>();
command.add(javaBin);
command.addAll(jvmArgs);
command.add("-cp");
command.add(classpath);
command.add(className);
command.addAll(args);
return new ProcessBuilder(command);
}
```
利用上面的函数,可以运行 classpath 上的任意 class。这篇文章中,用来在集成测试中启动新的子进程,无需提前构建 jar。这种方法可以控制 JVM 参数,比如子进程内存大小。传统的直接调用无法对此进行配置。
标签:Java,ProcessBuilder,List,add,command,进程,java,运行 来源: https://blog.51cto.com/u_15127686/2832720