在这一点上,我们可以安全地假设您想从 Java 应用程序中启动一些子进程,并且您在这里花了一些时间来正确地完成它。您查看 Commons Exec 并认为“哇 - 调用 Runtime.exec() 很容易,而 Apache 人员正在用大量代码浪费他们和我的时间”。
好吧,我们通过艰难的方式(在我的例子中不止一次)了解到使用普通的 Runtime.exec() 可能是一种痛苦的经历。因此,邀请您深入研究 commons-exec 并以简单的方式查看艰难的课程......
让我们看一个真实的例子 - 我们想从您的 Java 应用程序中打印 PDF 文档。谷歌搜索一段时间后,结果证明是一个小问题,使用 Adobe Acrobat 似乎是一个不错的选择。
假设在路径中找到 Acrobat Reader,Windows 下的命令行应该类似于 “AcroRd32.exe /p /h file”。
String line = "AcroRd32.exe /p /h " + file.getAbsolutePath(); CommandLine cmdLine = CommandLine.parse(line); DefaultExecutor executor = new DefaultExecutor(); int exitValue = executor.execute(cmdLine);
您成功打印了第一个 PDF 文档,但最后抛出异常 - 发生了什么?糟糕,Acrobat Reader 在成功时返回退出值“1”,这通常被视为执行失败。所以我们必须调整我们的代码来修复这个奇怪的行为 —— 我们定义了 “1” 的退出值被认为是成功执行。
String line = "AcroRd32.exe /p /h " + file.getAbsolutePath(); CommandLine cmdLine = CommandLine.parse(line); DefaultExecutor executor = new DefaultExecutor(); executor.setExitValue(1); int exitValue = executor.execute(cmdLine);
您愉快地打印了一段时间,但现在您的应用程序阻塞了 - 您的打印子进程因某些明显或不那么明显的原因而挂起。开始很容易,但如果Acrobat Reader失控,告诉你打印失败是因为缺纸,该怎么办呢?!幸运的是,Commons-exec提供了一个看门狗来为您完成这项工作。下面是改进后的代码,它会在60秒后终止一个失控的进程。
String line = "AcroRd32.exe /p /h " + file.getAbsolutePath(); CommandLine cmdLine = CommandLine.parse(line); DefaultExecutor executor = new DefaultExecutor(); executor.setExitValue(1); ExecuteWatchdog watchdog = new ExecuteWatchdog(60000); executor.setWatchdog(watchdog); int exitValue = executor.execute(cmdLine);
嗯,代码运行了很长一段时间,直到一位新客户抱怨没有打印任何文件。花了半天时间才发现无法打印以下文件 ‘C:\Document and Settings\Documents\432432.pdf’。由于空格的原因,并且没有进一步引用,命令行实际上分成了以下代码片段
AcroRd32.exe /p /h C:\Document And Settings\documents\432432.pdf
作为一种快速修复方法,我们添加了双引号,告诉 commons-exec 将该文件作为单个命令行参数处理,而不是将其分成几个部分。
String line = "AcroRd32.exe /p /h \"" + file.getAbsolutePath() + "\""; CommandLine cmdLine = CommandLine.parse(line); DefaultExecutor executor = new DefaultExecutor(); executor.setExitValue(1); ExecuteWatchdog watchdog = new ExecuteWatchdog(60000); executor.setWatchdog(watchdog); int exitValue = executor.execute(cmdLine);
前面的问题源于Commons-exec试图将单个命令行字符串拆分为字符串数组(考虑单引号和双引号)。
归根结底,这很容易出错,所以我们建议增量地构建命令行-根据同样的推理,Ant文档不建议将单个命令行传递给exec目标(请参阅exec任务的弃用命令属性)
Map map = new HashMap(); map.put("file", new File("invoice.pdf")); CommandLine cmdLine = new CommandLine("AcroRd32.exe"); cmdLine.addArgument("/p"); cmdLine.addArgument("/h"); cmdLine.addArgument("${file}"); cmdLine.setSubstitutionMap(map); DefaultExecutor executor = new DefaultExecutor(); executor.setExitValue(1); ExecuteWatchdog watchdog = new ExecuteWatchdog(60000); executor.setWatchdog(watchdog); int exitValue = executor.execute(cmdLine);
请注意,我们正在传递一个用于扩展命令行参数的“java.io.File”实例-这允许动态转换结果文件名以匹配您的操作系统。
到目前为止,我们有一个工作的例子,但它不足以用于生产-因为它是阻塞的。
您的工作线程将阻塞,直到打印进程完成或被监视程序终止。
因此,异步执行打印作业将会起到作用。
在本例中,我们创建了一个‘ExecuteResultHandler’实例,并将其传递给‘Executor’实例,以便异步执行流程。
“result tHandler”拾取任何违规异常或进程退出代码。
CommandLine cmdLine = new CommandLine("AcroRd32.exe"); cmdLine.addArgument("/p"); cmdLine.addArgument("/h"); cmdLine.addArgument("${file}"); HashMap map = new HashMap(); map.put("file", new File("invoice.pdf")); commandLine.setSubstitutionMap(map); DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler(); ExecuteWatchdog watchdog = new ExecuteWatchdog(60*1000); Executor executor = new DefaultExecutor(); executor.setExitValue(1); executor.setWatchdog(watchdog); executor.execute(cmdLine, resultHandler); // some time later the result handler callback was invoked so we // can safely request the exit value int exitValue = resultHandler.waitFor();