在 Java 中更轻松地执行外部进程





4.00/5 (16投票s)
无忧地从 Java 代码执行任何外部进程

引言
有时,当您依赖于从 Java 代码执行外部进程时,实际让该代码正确运行可能非常痛苦。
尝试运行外部进程时存在几个陷阱
- 来自进程和到进程的输入和输出流未得到正确处理。
- 即使进程正在运行,您的应用程序也没有等待它完成。
- 您的应用程序通过简单地停止来等待进程完成。
- 进程已终止,但您没有收到结果代码。
- 您根本不知道如何实际运行外部进程!
如果这些问题曾经困扰过您,请继续阅读。
我将使用 DOS 提示符(在 WinXP 下)作为本文演示的外部进程。
源代码组成如下
- 可重用代码
dev.exec.util.ExecHelper
dev.exec.util.ExecProcessor
- 演示代码
dev.exec.tester.TestApplication
dev.exec.tester.TestFrame
想法很简单
TestApplication
创建一个TestFrame
的实例。TestFrame
显示给用户。- 用户请求执行命令处理器。
TestFrame
将自己注册到ExecHelper
作为ExecProcessor
。TestFrame
使用ExecHelper
运行命令处理器。ExecHelper
通知TestFrame
关于新的输出文本。- 用户使用
TestFrame
为命令处理器输入命令。 TestFrames
通知ExecHelper
关于新的用户输入。ExecHelper
通知命令处理器关于新的用户输入。- 当命令处理器正在运行时,循环执行 6 - 10。
- 当程序正在运行时,循环执行 3 - 11。
- 退出。
源文件已记录(至少 ExecHelper
和 ExecProcessor
),并且演示 ZIP 包含一个 JBuilder
项目以及编译后的类和一个 JAR 文件。
我现在将解释如何在可重用代码中实现该概念。
首先,我声明 ExecProcessor
作为一个接口,用于处理来自外部进程的所有事件。
public interface ExecProcessor {
// This method gets called when the process sends us a new input String..
public void processNewInput(String input);
// This method gets called when the process sends us a new error String..
public void processNewError(String error);
// This method gets called when the process has ended..
public void processEnded(int exitValue);
}
实现 ExecProcessor
接口的 TestFrame
具有以下方法
public void processNewInput(String input) {
updateTextArea(jTextArea1, input);
}
public void processNewError(String error) {
updateTextArea(jTextArea1, error);
}
public processEnded(int exitValue) {
exh = null;
statusBar.setText("Command.exe ended..");
JOptionPane.showMessageDialog(this, "Exit value for Command.exe was ["
+ exitValue + "]", "Command.exe is done!", JOptionPane.INFORMATION_MESSAGE);
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
jTextArea1.setText(null);
statusBar.setText("Ready..");
}
如你所见,事情很简单。 processNewInput()
和 processNewError()
方法使用从命令处理器发送的新文本行更新一个 TextArea
,而 processEnded()
方法通知程序命令处理器已终止,并显示从命令处理器发送的退出值。
TestFrame
本身使用 runCommandActionPerformed()
方法调用 ExecHelper
的 exec()
方法,从而执行命令处理器,如下面的代码所示
void runCommandActionPerformed(ActionEvent e) {
if (exh == null) {
try {
exh = ExecHelper.exec(this, "cmd.exe");
statusBar.setText("Command.exe running..");
} catch (IOException ex) {
processNewError(ex.getMessage());
}
}
剩下的就是描述 ExecHelper
如何执行它的魔术。 首先,ExecHelper
是一个 Runnable
对象,这意味着它有一个 run()
方法,允许它在应用程序本身继续运行时执行其任务(即,多线程)。
ExecHelper
有三个流要处理,以及三个线程要处理。 是的,此版本的代码要求为每个进程激活三个单独的线程。 可能有一种方法可以用更少的线程来完成此操作,但目前我还没有时间进行研究。
作为一个 Runnable
对象,ExecHelper
的 run()
方法是最有趣的
public void run() {
// Are we on the process Thread?
if (processThread == Thread.currentThread()) {
try {
// This Thread just waits for the process to end and notifies the handler..
processEnded(process.waitFor());
} catch (InterruptedException ex) {
ex.printStackTrace();
}
// Are we on the InputRead Thread?
} else if (inReadThread == Thread.currentThread()) {
try {
// Read the InputStream in a loop until we find no more bytes to read..
for (int i = 0; i > -1; i = pInputStream.read(inBuffer)) {
// We have a new segment of input, so process it as a String..
processNewInput(new String(inBuffer, 0, i));
}
} catch (IOException ex) {
ex.printStackTrace();
}
// Are we on the ErrorRead Thread?
} else if (errReadThread == Thread.currentThread()) {
try {
// Read the ErrorStream in a loop until we find no more bytes to read..
for (int i = 0; i > -1; i = pErrorStream.read(errBuffer)) {
// We have a new segment of error, so process it as a String..
processNewError(new String(errBuffer, 0, i));
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
我建议你自己查看源代码,了解用户输入如何从 TestFrame
流向命令处理器。
我希望这对需要在 Java 中使用此功能的任何人都有用。
历史
- 2004 年 4 月 7 日:首次发布