我正在编写一个Java应用程序,需要使用Apache Commons Exec库来使用外部命令行应用程序。我需要运行的应用程序具有相当长的加载时间,因此最好保持一个实例处于活动状态而不是每次都创建一个新进程。应用程序的工作方式非常简单。一旦启动,它会等待一些新输入并生成一些数据作为输出,两者都使用应用程序的标准I / O.
因此,我们的想法是执行CommandLine,然后将PumpStreamHandler与三个独立的流(输出,错误和输入)一起使用,并使用这些流与应用程序进行交互。到目前为止,我已经在基本场景中完成了这项工作,我有一个输入,一个输出,然后应用程序关闭。但是,一旦我试图进行第二次交易,就会出现问题。
创建了我的CommandLine之后,我创建了我的Executor并按如下方式启动它:
this.executor = new DefaultExecutor();
PipedOutputStream stdout = new PipedOutputStream();
PipedOutputStream stderr = new PipedOutputStream();
PipedInputStream stdin = new PipedInputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(stdout, stderr, stdin);
this.executor.setStreamHandler(streamHandler);
this.processOutput = new BufferedInputStream(new PipedInputStream(stdout));
this.processError = new BufferedInputStream(new PipedInputStream(stderr));
this.processInput = new BufferedOutputStream(new PipedOutputStream(stdin));
this.resultHandler = new DefaultExecuteResultHandler();
this.executor.execute(cmdLine, resultHandler);
然后我继续启动三个不同的线程,每个线程处理一个不同的流。我还有三个处理输入和输出的SynchronousQueues(一个用作输入流的输入,一个用于通知outputQueue已启动新命令而另一个用于输出)。例如,输入流线程如下所示:
while (!killThreads) {
String input = inputQueue.take();
processInput.write(input.getBytes());
processInput.flush();
IOQueue.put(input);
}
如果我删除了while循环并只执行一次,那么一切似乎都完美无缺。显然,如果我再次尝试执行它,PumpStreamHandler会抛出异常,因为它已被两个不同的线程访问。
这里的问题是,在线程结束之前,似乎没有真正刷新processInput。调试时,命令行应用程序仅在线程结束时才真正接收其输入,但如果保留while循环则永远不会获得它。我已经尝试了许多不同的东西来使processInput刷新,但似乎没有任何效果。
之前有没有人尝试过类似的东西?有什么我想念的吗?任何帮助将不胜感激!
答案 0 :(得分:9)
我最终找到了一种方法来完成这项工作。通过查看Commons Exec库的代码,我注意到PumpStreamHandler使用的StreamPumpers在每次有新数据传入时都没有刷新。这就是我执行一次代码时代码工作的原因,因为它会自动刷新并关闭流。所以我创建了一个名为AutoFlushingStreamPumper和AutoFlushingPumpStreamHandler的类。后者与普通的PumpStreamHandler相同,但使用AutoFlushingStreamPumpers而不是通常的。 AutoFlushingStreamPumper与标准StreamPumper的作用相同,但每次向其写入内容时都会刷新其输出流。
我已经对它进行了相当广泛的测试,似乎效果很好。感谢所有试图解决这个问题的人!
答案 1 :(得分:1)
出于我的目的,事实证明我只需要覆盖“ExecuteStreamHandler”。这是我的解决方案,它将stderr捕获到StringBuilder中,并允许您将内容流式传输到stdin并从stdout接收内容:
class SendReceiveStreamHandler implements ExecuteStreamHandler
你可以在GitHub here上看到整个班级作为要点。
答案 2 :(得分:0)
为了能够在流程的STDIN中编写多个命令,我创建了一个新的
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Map;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.lang3.CharEncoding;
public class ProcessExecutor extends DefaultExecutor {
private BufferedWriter processStdinput;
@Override
protected Process launch(CommandLine command, Map env, File dir) throws IOException {
Process process = super.launch(command, env, dir);
processStdinput = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), CharEncoding.UTF_8));
return process;
}
/**
* Write a line in the stdin of the process.
*
* @param line
* does not need to contain the carriage return character.
* @throws IOException
* in case of error when writing.
* @throws IllegalStateException
* if the process was not launched.
*/
public void writeLine(String line) throws IOException {
if (processStdinput != null) {
processStdinput.write(line);
processStdinput.newLine();
processStdinput.flush();
} else {
throw new IllegalStateException();
}
}
}
要使用这个新的Executor,我将管道流保存在PumpStreamHandler中,以避免PumpStreamHandler接近STDIN。
ProcessExecutor executor = new ProcessExecutor();
executor.setExitValue(0);
executor.setWorkingDirectory(workingDirectory);
executor.setWatchdog(new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT));
executor.setStreamHandler(new PumpStreamHandler(outHanlder, outHanlder, new PipedInputStream(new PipedOutputStream())));
executor.execute(commandLine, this);
您可以使用执行程序writeLine()方法或创建自己的方法。