Apache Commons Exec教程

如果你看到这篇文章,相信你肯定使用java新建过子进程,去执行shell命令,并且肯定耗费了不少时间。你可能会认为java自身的Runtime.exec()很简单,而Apache Commons Exec太过臃肿,纯粹是在浪费时间。

但是,我在使用Runtime.exec()的过程时,经历了一系列痛苦的过程。一起来看下commons exec是怎么把这一过程变简单的。

第一个例子

下面是一个简单的例子,我们调用了命令行 AcorRd32.exe,当然前提是你的系统中有这个东西。

    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);

是否使用Watchdog

我们的程序输出了pdf内容。但是如果在输出过程中,我们的程序因为各种原因hang住了,怎么办?启动任务很简单,但是如果子进程失败了怎么办。Commons-exec提供了watchdog来解决这一问题。下面的代码中,如果子进程运行超过6秒,那么就强制结束它。

    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);

引号很有用

上面的代码还有个问题,如果输入的文件路径中含有空格,会有异常。

    > AcroRd32.exe /p /h C:\Document And Settings\documents\432432.pdf  

解决方法是,使用引号将文件路径括起来:

    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);

逐步生成CommandLine对象

上面的引号问题,是因为commons-exec错误地分割了我们的路径字符串,导致文件路径错误,我们可是使用CommandLine对象提供的方法,来避免手动拼装命令。

    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);

异步执行

现在为止,我们的程序可以良好运行了,但是还不适用生产,原因是它是阻塞的,在子进程结束前,它会一直阻塞我们的主进程。现在我们要把它变成非阻塞的。下面的代码,我们创建了一个ExecuteResultHandler对象,将它传给Executor,让它可以异步执行任务。ResultHandler会接受子进程抛出的一切异常。

    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);

    //一段时间后,resultHandler的回调方法会被唤醒,我们可以查看其返回值
    int exitValue = resultHandler.waitFor();

本教程中中的打印任务,一份完整的代码参考此处,http://commons.apache.org/proper/commons-exec/xref-test/org/apache/commons/exec/TutorialTest.html

获取程序输出的例子

    package com.yeetrack.weixinalert;

    import org.apache.commons.exec.CommandLine;
    import org.apache.commons.exec.DefaultExecutor;
    import org.apache.commons.exec.PumpStreamHandler;

    import java.io.ByteArrayOutputStream;
    import java.io.IOException;

    /**
    * Created by Xuemeng Wang on 15/4/2.
    */
    public class ShellUtil {
        DefaultExecutor defaultExecutor;
        ByteArrayOutputStream outputStream;
        ByteArrayOutputStream errorStream;
        PumpStreamHandler pumpStreamHandler;

        public ShellUtil()
        {
            defaultExecutor = new DefaultExecutor();
            outputStream = new ByteArrayOutputStream();
            errorStream = new ByteArrayOutputStream();
            pumpStreamHandler = new PumpStreamHandler(outputStream, errorStream);
            defaultExecutor.setStreamHandler(pumpStreamHandler);
        }

        public void execute(CommandLine commandLine)
        {
            try {
                defaultExecutor.execute(commandLine);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        public String getOutAsString()
        {
            return outputStream.toString();
        }
        public String getErrorAsString()
        {
            return errorStream.toString();
        }
    }

发表评论

电子邮件地址不会被公开。 必填项已用*标注

(Spamcheck Enabled)