" />

警告:即将离开本站

点击"继续"将前往其他页面,确认后跳转。

侧边栏壁纸
  • 累计撰写 19 篇文章
  • 累计创建 2 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

使用Apache Commons Exec扩展Java外部执行进程

dengdz
2024-12-18 / 0 评论 / 0 点赞 / 31 阅读 / 0 字

在我当前的项目中,有一些场景需要在 Java 程序中调用外部命令和脚本,例如执行一些系统维护脚本、调用 Python 脚本或者 Go 脚本进行数据处理等。使用 Java 自带的 Runtime.exec() 或 ProcessBuilder 来执行这些命令通常很麻烦(日志打印、参数传递、进程管理等)。

而 Apache Commons Exec 提供了一个更加简洁和灵活的解决方案,它不仅可以轻松管理外部进程的启动、运行和结束,还能对输出流、错误流进行流式处理,极大地简化了代码的复杂度。

1. 引入Maven依赖

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-exec</artifactId>
  <version>1.3</version>
</dependency>

2. 执行命令

2.1. 通过构造方法创建

CommandLine cmdLine = new CommandLine()  
  1. 通过new直接创建出一个 CommandLine 对象

  2. 构造方法中可以传入脚本路径和其他的CommandLine

例:

public static void main(String[] args) {
        // 脚本路径
        String scriptPath = "E:\\dengdz-demo\\java-exec-script\\src\\main\\resources\\print.py";

        // 解析器路径
        String bashPath = "D:\\Python38\\python.exe";

        // 创建 CommandLine 对象,表示将要执行的命令
        CommandLine cmdLine = new CommandLine(bashPath);
        cmdLine.addArgument(scriptPath);

        // 创建 Executor 对象,负责执行命令
        Executor executor = new DefaultExecutor();

        // 捕获脚本执行输出
        PumpStreamHandler streamHandler = new PumpStreamHandler(System.out, System.err);
        executor.setStreamHandler(streamHandler);

        try {
            // 执行命令
            int exitValue = executor.execute(cmdLine);
            System.out.println("脚本执行成功,退出码:" + exitValue);
        } catch (ExecuteException e) {
            System.err.println("执行脚本失败,错误码:" + e.getExitValue());
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("IO 错误");
            e.printStackTrace();
        }
    }

2.2. 通过静态方法解析命令

CommandLine cmdLine = CommandLine.parse()
  1. 可以直接传入一行命令

  2. 可以将参数和值作为map进行传入,提高代码可读性

例:

public static void main(String[] args) {

        
        CommandLine cmdLine = CommandLine.parse("ping -c 4 192.168.1.1");

        DefaultExecutor executor = new DefaultExecutor();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PumpStreamHandler streamHandler = new PumpStreamHandler(out);
        executor.setStreamHandler(streamHandler);

        try {
            
            int exitValue = executor.execute(cmdLine);
            System.out.println("Command finished with exit code: " + exitValue);
            
            System.out.println(out.toString());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

3. 额外设置

3.1. 异步执行

使用 Executorexecute 方法与 ExecuteResultHandler 进行异步执行

public static void main(String[] args) throws IOException, InterruptedException {

        CommandLine commandLine = new CommandLine("ping");
        commandLine.addArgument("localhost");

        DefaultExecutor executor = new DefaultExecutor();
        executor.setExitValue(0);

        // 异步执行
        ExecuteResultHandler resultHandler = new ExecuteResultHandler() {
            @Override
            public void onProcessComplete(int exitValue) {
                System.out.println("进程已成功完成并带有退出值: " + exitValue);
            }

            @Override
            public void onProcessFailed(ExecuteException e) {
                System.err.println("进程失败: " + e.getMessage());
            }
        };

        System.out.println("正在启动进程...");
        executor.execute(commandLine, resultHandler);

        // 主线程继续执行
        System.out.println("在进程运行时执行其他操作...");
        Thread.sleep(10000); // 等待一段时间

    }
  • ExecuteResultHandler 提供了 onProcessCompleteonProcessFailed 方法处理进程结束的情况。

  • 主线程可以继续执行其他任务,而外部进程在后台运行。

3.2. 设置环境变量

为外部命令设置自定义的环境变量

public static void main(String[] args) throws IOException {

        CommandLine commandLine = new CommandLine("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"); // Windows 系统下打印环境变量

        DefaultExecutor executor = new DefaultExecutor();

        commandLine.addArgument("echo $Env:PATH");

        // 设置环境变量
        Map<String, String> env = new HashMap<>();
        env.put("MY_CUSTOM_ENV", "HelloWorld");

        executor.execute(commandLine, env);

    }
  • 使用 execute 方法的 env 参数来传递自定义环境变量。

  • 这里使用了 echo $Env:PATH 命令来打印Windows环境变量

3.3. 设置工作目录

为外部命令设置指定的工作目录

public static void main(String[] args) {

        CommandLine commandLine = new CommandLine("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"); // Windows 系统下打印环境变量

        commandLine.addArgument("dir");

        DefaultExecutor executor = new DefaultExecutor();
        executor.setExitValue(0);

        // 设置工作目录
        File workingDirectory = new File("E:\\");
        executor.setWorkingDirectory(workingDirectory);


        try {
            // 执行命令
            int exitValue = executor.execute(commandLine);
            System.out.println("脚本执行成功,退出码:" + exitValue);
        } catch (ExecuteException e) {
            System.err.println("执行脚本失败,错误码:" + e.getExitValue());
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("IO 错误");
            e.printStackTrace();
        }


    }
  • 使用 setWorkingDirectory 设置执行命令的目录。

3.4. 设置超时时间

设置外部命令执行的最大超时时间,防止进程长时间挂起

public static void main(String[] args) {

        CommandLine commandLine = new CommandLine("ping");
        commandLine.addArgument("localhost");

        DefaultExecutor executor = new DefaultExecutor();
        executor.setExitValue(0);

        // 设置超时时间(单位:毫秒)
        ExecuteWatchdog watchdog = new ExecuteWatchdog(5000); // 5秒超时
        executor.setWatchdog(watchdog);


        try {
            // 执行命令
            int exitValue = executor.execute(commandLine);
            System.out.println("脚本执行成功,退出码:" + exitValue);
        } catch (ExecuteException e) {
            System.err.println("执行脚本失败,错误码:" + e.getExitValue());
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("IO 错误");
            e.printStackTrace();
        }

        // 检查进程是否被终止
        if (watchdog.killedProcess()) {
            System.err.println("由于超时,进程被终止.");
        }

    }
  • 使用 ExecuteWatchdog 来设置超时时间。

  • 如果进程运行超过 5 秒,ExecuteWatchdog 会自动终止它。

3.5. 监听结果

使用 ExecuteWatchdogExecuteResultHandler 结合,可以监听进程的退出状态

public static void main(String[] args) {

        CommandLine commandLine = new CommandLine("ping");
        commandLine.addArgument("115.116.117.1181");

        DefaultExecutor executor = new DefaultExecutor();
        executor.setExitValue(0);

        // 设置超时时间(单位:毫秒)
        ExecuteWatchdog watchdog = new ExecuteWatchdog(10000); // 10秒超时
        executor.setWatchdog(watchdog);


        try {
            // 监听结果
            ExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
            executor.execute(commandLine, resultHandler);
        } catch (ExecuteException e) {
            System.err.println("执行脚本失败,错误码:" + e.getExitValue());
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("IO 错误");
            e.printStackTrace();
        }

        // 检查进程是否被终止
        if (watchdog.killedProcess()) {
            System.err.println("由于超时,进程被终止.");
        }

    }

0

评论区