我本周需要将Java类(而不是jar)作为子进程运行。 更准确地说,我想从测试内部产生一个新进程,而不是直接在测试内部运行(进程内)。 我不认为这是幻想或复杂的事情。 但是,这不是我以前不需要做的事,也不知道要编写的确切代码。
幸运的是,稍后有一个快速的Google和一些Stack Overflow帖子。 我找到了所需的答案 。 尽管有答案,但为了我自己和你自己的利益,我在这里重写了它。
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
方法期望的参数一起使用。 可以访问两组参数可以完全控制子流程的执行。 例如,您可能想以低堆空间执行您的类,以查看它在内存压力下如何应对(这是我需要的)。
注意,要使此方法起作用,您要执行的类需要具有一个main
方法。 这很重要。
访问Java可执行文件的路径(存储在javaBin
)允许您使用与主应用程序相同的Java版本执行子javaBin
。 如果将javaBin
替换为"java"
,则存在使用计算机的默认Java版本执行子javaBin
的风险。 很多时候那可能很好。 但是,在某些情况下可能不希望这样做。
将所有命令添加到command
列表后,它们将传递到ProcessBuilder
。 ProcessBuilder
获取此列表,并使用其中包含的每个值来生成命令。 command
列表中的每个值都由ProcessBuilder
用空格分隔。 它的构造函数还有其他重载,其中之一包含一个字符串,您可以在其中手动定义整个命令。 这消除了您手动管理在命令字符串中添加参数的需要。
子进程以其IO传递到执行它的进程开始。 查看生成的所有stdout
和stderr
都是必需的。 inheritIO
是一种便捷方法,也可以通过调用链接以下代码来实现(也配置子inheritIO
的stdin
):
builder
.redirectInput(ProcessBuilder.Redirect.INHERIT)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT);
最后, waitFor
告诉执行线程等待所生成的子进程完成。 该过程是否成功结束或错误都无关紧要。 只要子流程以某种方式完成。 主要执行可以继续进行。 进程如何完成由其exitValue
详细说明。 例如, 0
通常表示成功执行,而1
详细说明无效语法错误。 还有许多其他退出代码,它们在不同的应用程序中可能会有所不同。
调用exec
方法如下所示:
JavaProcess.exec(MyProcess. class , List.of( "-Xmx200m" ), List.of( "argument" ))
它执行以下命令(或其附近的命令):
/Library/Java/JavaVirtualMachines/jdk- 12.0 . 1 .jdk/Contents/Home/bin/java -cp /playing-around- -blogs MyProcess for -blogs MyProcess "argument"
我剪掉了很多包含classpath的路径,以使其更加简洁。 您的外观可能会比这更长。 这实际上取决于您的应用程序。 上面命令中的路径是运行它所需的最低要求(显然是为我的机器定制的)。
exec
方法相当灵活,有助于描述发生的情况。 尽管,如果您希望使其具有更大的延展性和适用性,但我建议从该方法返回ProcessBuilder
本身。 允许您在多个地方重用这段代码,同时提供配置IO重定向的灵活性,以及决定是在后台还是在块中运行子流程并等待其完成的能力。 这看起来像:
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); }
通过使用这两个功能中的一个(或两个),您现在可以运行应用程序的类路径中存在的任何类。 在我的情况下,这对于在集成测试中生成子流程非常有用,而无需预先构建任何jar。 这样就可以控制JVM参数,例如子进程的内存,如果直接在现有进程内部运行,则这些子进程将无法配置。