Java—用ProcessBuilder执行Shell任务
![](https://img.haomeiwen.com/i3154067/cfe67981283445e8.jpg)
知乎编辑器效果有限,原文发布在语雀文档上,看上去效果更好~
Java—用ProcessBuilder执行Shell任务 · 语雀www.yuque.com
![](https://img.haomeiwen.com/i3154067/53a7e49b4dbdf6cc.jpg)
概述
在Java中执行Shell任务可以用两种方式:1.java.lang下的Runtime 2.java.lang下的ProcessBuilder
但是,通过源码可以发现,二者最终都是通过ProcessBuilder类来执行操作的。为了在Java中执行大数据中的shell任务,添加任务失败重试次数、成功/失败消费者,我们可以设计如下几个类:
- ShellMonitor
- ShellTask
- Task
- StreamGobbler
ShellMonitor
主要是ShellTask的监控和管理,定义了失败重试方法、任务失败/成功后的消费者、以及Task实体对象
ShellTask
执行任务的主体,继承了抽象类Task
Task
抽象类,定义了所有任务的公共属性:任务重试次数:retryNum、cmdTextConsumer(cmd输出消费者)、任务失败后的回调(消费者)failedCallback、任务成功后的回调(successCallback)
StreamGobbler
日志流接收类,用于处理ProcessBuilder执行时的输入/输出/错误流
ProcessBuilder
Java.lang包下的,用于在Java中执行shell的类,文章后有详细介绍。
任务执行示例
![](https://img.haomeiwen.com/i3154067/5d8224ed0881c6c1.png)
启动一个Shell任务很简单,就2句:
ShellTask shellTask = new ShellTask(confList.toArray(new String[confList.size()]));
new ShellMonitor<String>(shellTask, logger::info, null, null).run();
1.将List<String>类型的commands转化为String[],并new一个ShellTask任务
2.new一个ShellMonitor将该ShellTask放进去,配置任务成功/失败消费者为空,然后执行run()
相关类说明
1.ShellMonitor
![](https://img.haomeiwen.com/i3154067/13ecf42a51f7a25b.jpg)
主要属性
task
即任务执行的主体
failedTimes
属性记录了Task失败次数
failureConsumer successConsumer
则分别对应失败/成功后的消费者(消费者还是个Task任务)
主要方法
run()
主要的方法run(),调用task的run()
redo()
失败重试方法,在失败次数 < Task中定义的失败重试次数时,调用redo()方法
2.ShellTask
ShellTask即任务执行的主体类,继承了抽象类Task,并重新了run()和redo()方法
![](https://img.haomeiwen.com/i3154067/5ec8915cc325fda9.jpg)
主要属性
commands
String[],装的是执行的参数集合
path
执行shell时需要用到的文件
构造函数
三种构造器:
无参、带String[]参数、带File和String[]构造器
主要方法
run()
run方法即执行的主方法,在其中调用runCommand()具体执行
redo()
失败重试方法,在其中调用runCommand()具体执行
runCommand()
执行的主方法,具体如下:
public void runCommand() {
try {
logger.info("start");
long start = System.nanoTime();
ProcessBuilder builder = new ProcessBuilder();
builder.command(commands);
builder.directory(path == null ? FileUtils.getUserDirectory() : path);
//启动工作线程
Process process = builder.start();
//启动日志流记录线程
StreamGobbler streamGobbler =
new StreamGobbler(process.getInputStream(), process.getErrorStream(), super.cmdTextConsumer);
new Thread(streamGobbler).start();
int exitCode = process.waitFor();
long end = System.nanoTime();
if (exitCode == 0) {
if (successCallback != null)
super.successCallback.accept(this);
} else {
if (failedCallback != null)
super.failedCallback.accept(this);
}
logger.info("ec=" + exitCode);
logger.info("end");
logger.info("duration:" + ((double) (end - start) / 10e8));
} catch (Exception e) {
e.printStackTrace();
}
}
程序逻辑很简单,启动ProcessBuilder工作线程、启动StreamGobbler记录线程。通过exitCode获取工作线程的执行状态码,0表示运行正常结束,如果正常结束则调用任务成功消费者、否则调用失败消费者(如果定义了消费者)
3.Task
Task是一个抽象类实现了Runnable接口,提供了所有任务的抽象方法和通用属性字段
![](https://img.haomeiwen.com/i3154067/68bda91b5286d241.jpg)
主要属性
retryNum
任务失败重试次数,默认为3次
failedCallback
任务失败时的消费者
successCallback
任务成功时的消费者
构造函数
三种构造器:
无参、带String[]参数、带File和String[]构造器
主要方法
run()
重新runnable接口中的run()
redo()
失败重试方法
4.StreamGobbler
实现了Runnable接口的一个线程类,用于任务执行过程中的输出流、错误流
![](https://img.haomeiwen.com/i3154067/2619f7f68bf22b3e.jpg)
主要run方法如下:
public void run() {
Runnable inputRunnable = () -> new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(consumer);
Runnable errorRunnable = () -> new BufferedReader(new InputStreamReader(errorStream)).lines().forEach(consumer);
new Thread(inputRunnable).start();
new Thread(errorRunnable).start();
}
此类分别启动两个线程,用于直接打印任务运行时的正常输出和错误输出
5.ProcessBuilder
ProcessBuilder类是Java1.5在java.lang中新添加的一个新类,此类用于创建操作系统进程,它提供一种启动和管理进程(也就是应用程序)的方法。在1.5之前,都是由Process类处理实现进程的控制管理。每个 ProcessBuilder 实例管理一个进程属性集。它的start() 方法利用这些属性创建一个新的 Process 实例。start() 方法可以从同一实例重复调用,以利用相同的或相关的属性创建新的子进程。
具体可以参考:https://blog.csdn.net/u013256816/article/details/54603910
主要属性
点进去ProcessBuilder的源码看看:
![](https://img.haomeiwen.com/i3154067/a7544b306d5eb070.jpg)
可见有五个属性,其中command是shell命令,directory是shell中会用到的文件,environment环境配置,还有个布尔值,是否重定向错误流,以及redirects[]重定向数组。一般情况下,只需要用到command。
构造函数
查看类的构造器,可以看到有两种:
![](https://img.haomeiwen.com/i3154067/77081c74c7f7a06b.jpg)
这两个构造器中command字段,分别接受List<String>和String[]类型的参数,最终都会将command转化为List<String>类型
主要方法
略...
好久没写文章了,一是工作忙了,最主要是人变懒了,不过我又回来了,后面的文章还是会不断出来~重点是Java基本经典的书、读书笔记类型的,过一遍然后Java就暂时放一边(项目中比较常用的,经典的代码还是会记录下)。