运行 带有 Processbuilder 的 jar 无法正常工作
Running jar with Processbuilder doesn't work properly
我有以下代码:
ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
Process proc = pb.start();
当 运行 使用此命令从我的终端生成 jar 文件时:
java -jar test.jar 135 3 3 appName
那么它就像一个魅力。 jar 在我的数据库中推送了一些东西,所以我看到它正在工作。但是,当我使用上面提到的 processBuilder 代码从我的 JavaServlet 执行此操作时,我的数据库中没有任何数据,我也没有收到任何错误。
但是进程本身是 运行,我在终端中用 "ps ax" 检查了它。所以我想知道这里的区别在哪里?我做错了什么?
有人有想法吗?
编辑: 更多代码:
ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
Process proc = pb.start();
System.out.println( "Job running" );
proc.waitFor(); // wait until jar is finished
System.out.println( "Job finished" );
InputStream in = proc.getInputStream();
InputStream err = proc.getErrorStream();
byte result[] = new byte[ in.available() ];
in.read( result, 0, result.length );
System.out.println( new String( result ) );
byte error[] = new byte[ err.available() ];
err.read( error, 0, error.length );
System.out.println( new String( error ) );
更新:
我试图调用 shell-script 而不是我的 jar。所以我从我的 java-file.shell 调用了一个带有 processbuilder 的脚本。
我的 shell 脚本是这样做的:
java -jar test.jar "" "" "" ""
嗯,还是不行。所以我尝试了这个:
gnome-terminal -x java -jar test.jar "" "" "" ""
突然间它起作用了!! 但是 它会打开执行 jar 文件的 gnome 终端。
所以我想知道,这与 eclipse 中未显示的输出有什么关系吗?我真的不明白。现在这是一个很好的解决方法。但我真的很想在每次执行 jar 时不打开终端的情况下运行它。
你能试试这个吗?
更新
代码:
// Java runtime
Runtime runtime = Runtime.getRuntime();
// Command
String[] command = {"java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application};
// Process
Process process = runtime.exec(command, null, new File("/home/userName/TestBSC"));
首先,我无法重现你的问题,所以这个答案将完全基于文档。
By default, the created subprocess does not have its own terminal or
console. All its standard I/O (i.e. stdin, stdout, stderr) operations
will be redirected to the parent process, where they can be accessed
via the streams obtained using the methods getOutputStream(),
getInputStream(), and getErrorStream(). The parent process uses these
streams to feed input to and get output from the subprocess. Because
some native platforms only provide limited buffer size for standard
input and output streams, failure to promptly write the input stream
or read the output stream of the subprocess may cause the subprocess
to block, or even deadlock.
java.lang.Process Documentation
基本上这告诉您需要正确处理外部进程的流,否则在某些平台上可能会导致死锁。这意味着如果我们 运行 的命令产生一些输出,您必须读取该输出。
所以让我们看一下您的代码;您正在调用 process.waitFor()
等待进程完成,但问题是您的 进程无法完成 没有您 read/consume 它的输出因此您正在创建死锁。
如何克服这个问题:
方法是使用 InputStreamConsumerThread
正确处理 input/error 流;
public class InputStreamConsumerThread extends Thread
{
private InputStream is;
private boolean sysout;
private StringBuilder output = new StringBuilder();
public InputStreamConsumerThread (InputStream is, boolean sysout)
{
this.is=is;
this.sysout=sysout;
}
public void run()
{
try(BufferedReader br = new BufferedReader(new InputStreamReader(is)))
{
for (String line = br.readLine(); line != null; line = br.readLine())
{
if (sysout)
System.out.println(line);
output.append(line).append("\n");
}
}
}
public String getOutput(){
return output.toString();
}
}
您的代码将是;
String systemProperties = "-Dkey=value";
ProcessBuilder pb = new ProcessBuilder( "java", systemProperties, "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
Process proc = pb.start();
InputStreamConsumerThread inputConsumer =
new InputStreamConsumerThread(proc.getInputStream(), true);
InputStreamConsumerThread errorConsumer =
new InputStreamConsumerThread(proc.getErrorStream(), true);
inputConsumer.start();
errorConsumer.start();
System.out.println( "Job running" );
proc.waitFor(); // wait until jar is finished
System.out.println( "Job finished" );
inputConsumer.join(); // wait for consumer threads to read the whole output
errorConsumer.join();
String processOutput = inputConsumer.getOutput();
String processError = errorConsumer.getOutput();
if(!processOutput.isEmpty()){
//there were some output
}
if(!processError.isEmpty()){
//there were some error
}
方法是使用ProcessBuilder
重定向输出。如果您只想让您的子流程与您的父流程使用相同的 input/output 流,您可以像这样使用 ProcessBuilder.Redirect.INHERIT
;
ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
pb.redirectErrorStream(true); // redirect error stream to output stream
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process proc = pb.start();
System.out.println( "Job running" );
//since process builder will handle the streams for us
//we can call waitFor() safely
proc.waitFor();
System.out.println( "Job finished" );
方法是使用第 3 方库。如果您不想自己麻烦 ProcessBuilder
和消费者线程,我知道有 2 个库可以很好地处理创建子流程。
A low-overhead, non-blocking I/O, external Process execution
implementation for Java. It is a replacement for
java.lang.ProcessBuilder and java.lang.Process.
Have you ever been annoyed by the fact that whenever you spawn a
process in Java you have to create two or three "pumper" threads (for
every process) to pull data out of the stdout and stderr pipes and
pump data into stdin? If your code starts a lot of processes you can
have dozens or hundreds of threads doing nothing but pumping data.
NuProcess uses the JNA library to use platform-specific native APIs to
achive non-blocking I/O on the pipes between your Java process and the
spawned processes.
There are many approaches to take when running external processes from
Java. There are the JRE options such as the Runtime.exec() and
ProcessBuilder. Also there is the Apache Commons Exec. Nevertheless we
created yet another process library (YAPL).
Some of the reasons for this crazy endeavour
Improved handling of streams Reading/writing to streams Redirecting
stderr to stdout Improved handling of timeouts Improved checking of
exit codes Improved API One liners for quite complex usecases One
liners to get process output into a String Access to the Process
object available Support for async processes ( Future ) Improved
logging with SLF4J API Support for multiple processes
Java 的流程 API 中还有其他陷阱,请查看这篇 JavaWorld 文章了解更多信息 When Runtime.exec() won't。
我有以下代码:
ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
Process proc = pb.start();
当 运行 使用此命令从我的终端生成 jar 文件时:
java -jar test.jar 135 3 3 appName
那么它就像一个魅力。 jar 在我的数据库中推送了一些东西,所以我看到它正在工作。但是,当我使用上面提到的 processBuilder 代码从我的 JavaServlet 执行此操作时,我的数据库中没有任何数据,我也没有收到任何错误。
但是进程本身是 运行,我在终端中用 "ps ax" 检查了它。所以我想知道这里的区别在哪里?我做错了什么?
有人有想法吗?
编辑: 更多代码:
ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
Process proc = pb.start();
System.out.println( "Job running" );
proc.waitFor(); // wait until jar is finished
System.out.println( "Job finished" );
InputStream in = proc.getInputStream();
InputStream err = proc.getErrorStream();
byte result[] = new byte[ in.available() ];
in.read( result, 0, result.length );
System.out.println( new String( result ) );
byte error[] = new byte[ err.available() ];
err.read( error, 0, error.length );
System.out.println( new String( error ) );
更新:
我试图调用 shell-script 而不是我的 jar。所以我从我的 java-file.shell 调用了一个带有 processbuilder 的脚本。
我的 shell 脚本是这样做的:
java -jar test.jar "" "" "" ""
嗯,还是不行。所以我尝试了这个:
gnome-terminal -x java -jar test.jar "" "" "" ""
突然间它起作用了!! 但是 它会打开执行 jar 文件的 gnome 终端。
所以我想知道,这与 eclipse 中未显示的输出有什么关系吗?我真的不明白。现在这是一个很好的解决方法。但我真的很想在每次执行 jar 时不打开终端的情况下运行它。
你能试试这个吗?
更新
代码:
// Java runtime
Runtime runtime = Runtime.getRuntime();
// Command
String[] command = {"java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application};
// Process
Process process = runtime.exec(command, null, new File("/home/userName/TestBSC"));
首先,我无法重现你的问题,所以这个答案将完全基于文档。
By default, the created subprocess does not have its own terminal or console. All its standard I/O (i.e. stdin, stdout, stderr) operations will be redirected to the parent process, where they can be accessed via the streams obtained using the methods getOutputStream(), getInputStream(), and getErrorStream(). The parent process uses these streams to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, or even deadlock.
java.lang.Process Documentation
基本上这告诉您需要正确处理外部进程的流,否则在某些平台上可能会导致死锁。这意味着如果我们 运行 的命令产生一些输出,您必须读取该输出。
所以让我们看一下您的代码;您正在调用 process.waitFor()
等待进程完成,但问题是您的 进程无法完成 没有您 read/consume 它的输出因此您正在创建死锁。
如何克服这个问题:
方法是使用
InputStreamConsumerThread
正确处理 input/error 流;public class InputStreamConsumerThread extends Thread { private InputStream is; private boolean sysout; private StringBuilder output = new StringBuilder(); public InputStreamConsumerThread (InputStream is, boolean sysout) { this.is=is; this.sysout=sysout; } public void run() { try(BufferedReader br = new BufferedReader(new InputStreamReader(is))) { for (String line = br.readLine(); line != null; line = br.readLine()) { if (sysout) System.out.println(line); output.append(line).append("\n"); } } } public String getOutput(){ return output.toString(); } }
您的代码将是;
String systemProperties = "-Dkey=value";
ProcessBuilder pb = new ProcessBuilder( "java", systemProperties, "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application );
pb.directory( new File("/home/userName/TestBSC") );
Process proc = pb.start();
InputStreamConsumerThread inputConsumer =
new InputStreamConsumerThread(proc.getInputStream(), true);
InputStreamConsumerThread errorConsumer =
new InputStreamConsumerThread(proc.getErrorStream(), true);
inputConsumer.start();
errorConsumer.start();
System.out.println( "Job running" );
proc.waitFor(); // wait until jar is finished
System.out.println( "Job finished" );
inputConsumer.join(); // wait for consumer threads to read the whole output
errorConsumer.join();
String processOutput = inputConsumer.getOutput();
String processError = errorConsumer.getOutput();
if(!processOutput.isEmpty()){
//there were some output
}
if(!processError.isEmpty()){
//there were some error
}
方法是使用
ProcessBuilder
重定向输出。如果您只想让您的子流程与您的父流程使用相同的 input/output 流,您可以像这样使用ProcessBuilder.Redirect.INHERIT
;ProcessBuilder pb = new ProcessBuilder( "java", "-jar", "test.jar", Integer.toString( jobId ), Integer.toString( software ), Integer.toString( entryPoint ), application ); pb.directory( new File("/home/userName/TestBSC") ); pb.redirectErrorStream(true); // redirect error stream to output stream pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); Process proc = pb.start(); System.out.println( "Job running" ); //since process builder will handle the streams for us //we can call waitFor() safely proc.waitFor(); System.out.println( "Job finished" );
方法是使用第 3 方库。如果您不想自己麻烦
ProcessBuilder
和消费者线程,我知道有 2 个库可以很好地处理创建子流程。
A low-overhead, non-blocking I/O, external Process execution implementation for Java. It is a replacement for java.lang.ProcessBuilder and java.lang.Process.
Have you ever been annoyed by the fact that whenever you spawn a process in Java you have to create two or three "pumper" threads (for every process) to pull data out of the stdout and stderr pipes and pump data into stdin? If your code starts a lot of processes you can have dozens or hundreds of threads doing nothing but pumping data.
NuProcess uses the JNA library to use platform-specific native APIs to achive non-blocking I/O on the pipes between your Java process and the spawned processes.
There are many approaches to take when running external processes from Java. There are the JRE options such as the Runtime.exec() and ProcessBuilder. Also there is the Apache Commons Exec. Nevertheless we created yet another process library (YAPL).
Some of the reasons for this crazy endeavour
Improved handling of streams Reading/writing to streams Redirecting stderr to stdout Improved handling of timeouts Improved checking of exit codes Improved API One liners for quite complex usecases One liners to get process output into a String Access to the Process object available Support for async processes ( Future ) Improved logging with SLF4J API Support for multiple processes
Java 的流程 API 中还有其他陷阱,请查看这篇 JavaWorld 文章了解更多信息 When Runtime.exec() won't。