在我当前的项目中,有一些场景需要在 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()
通过new直接创建出一个 CommandLine 对象
构造方法中可以传入脚本路径和其他的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()
可以直接传入一行命令
可以将参数和值作为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. 异步执行
使用 Executor
的 execute
方法与 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
提供了onProcessComplete
和onProcessFailed
方法处理进程结束的情况。主线程可以继续执行其他任务,而外部进程在后台运行。
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. 监听结果
使用 ExecuteWatchdog
和 ExecuteResultHandler
结合,可以监听进程的退出状态
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("由于超时,进程被终止.");
}
}
评论区