博主在最近的工作中,收到了这样一个需求。
调用别人以前完成开发的 jar 包或 python 程序,并将原程序在命令行中输出的内容封装为 JSON 对象后通过 RESTFul 接口返回。
面对以上的需求,博主给出了以下解决方案。话不多说,上代码。
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.*;
/**
* <p>
* Java 在执行 Runtime.getRuntime().exec(command)之后,Linux会创建一个进程,
* 该进程与JVM进程建立三个管道连接,标准输入流、标准输出流、标准错误流。
* </p>
*
* @author SupremeSir
* @since 2022/11/27
*/
@Slf4j
public class ShellCommandExecutor {
/**
* 创建线程池
*/
private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(2, 4, 20,TimeUnit.MINUTES, new ArrayBlockingQueue<>(10), new ThreadPoolExecutor.CallerRunsPolicy());
/**
* @param cmd 命令行内容
*/
public static ShellExecutorResultBean execShellStrWithResult(String cmd) {
// 命令行输出的正常信息
String sysOutInfo = null;
// 命令行输出的错误信息
String sysOutError = null;
// 判断系统环境
boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
// 组装命令行命令
String[] cmdArray = {isWindows ? "cmd" : "/bin/sh", isWindows ? "/c" : "-c", cmd};
String charset = isWindows ? "GBK" : "UTF-8";
try {
// 执行命令
Process process = Runtime.getRuntime().exec(cmdArray);
// 启动子线程,读取命令行输出的正常信息
FutureTask<String> sysOutTask = new FutureTask<>(() -> {
StringBuilder builder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), charset))) {
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return builder.toString();
});
THREAD_POOL_EXECUTOR.execute(sysOutTask);
// 启动子线程,命令行输出的错误信息
FutureTask<String> sysErrorTask = new FutureTask<>(() -> {
StringBuilder builder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), charset))) {
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return builder.toString();
});
THREAD_POOL_EXECUTOR.execute(sysErrorTask);
log.info("命令行执行进程开始等待");
process.waitFor();
// 获取命令行输出信息
sysOutInfo = sysOutTask.get();
sysOutError = sysErrorTask.get();
log.info("命令行执行进程结束等待");
// 判断命令行进程执行状态
if (process.isAlive()) {
process.destroy();
log.info("线程已销毁:" + process.isAlive());
} else {
log.info("线程已销毁:" + process.isAlive());
}
} catch (InterruptedException | IOException | ExecutionException e) {
e.printStackTrace();
}
// 组装返回结果
ShellExecutorResultBean resultBean = new ShellExecutorResultBean();
resultBean.setSysOutError(sysOutError);
resultBean.setSysOutInfo(sysOutInfo);
return resultBean;
}
/**
* 命令行输出结果包装类
*/
public static class ShellExecutorResultBean{
/**
* 命令行输出的正常信息
*/
private String sysOutInfo;
/**
* 命令行输出的错误信息
*/
private String sysOutError;
public String getSysOutInfo() {
return sysOutInfo;
}
public void setSysOutInfo(String sysOutInfo) {
this.sysOutInfo = sysOutInfo;
}
public String getSysOutError() {
return sysOutError;
}
public void setSysOutError(String sysOutError) {
this.sysOutError = sysOutError;
}
@Override
public String toString() {
return "ShellExecutorResultBean{" +
"sysOutInfo='" + sysOutInfo + '\'' +
", sysOutError='" + sysOutError + '\'' +
'}';
}
}
}
注意:
-
如果你不需要获取命令行的数据,也最好将命令行的输出信息、错误信息,都通过线程的方式获取一下,这样可以避免因输入流缓冲区灌满而导致的死锁问题。
-
如果你在
Linux
环境中执行调用,最好使用/bin/sh
解析shell
命令,否则可能也会导致线程莫名卡死的状态。 -
如果你使用的是
XShell
连接工具,同时规避了以上两个问题,但线程还是莫名其妙的卡死了,且收到 “转发X11
到Xmanager
” 的提示,则需在当前连接 “属性” 页面中的 “隧道” 选项卡中,取消勾选“转发X11到”这个选项,如下图所示:
- 如果你的程序不死锁了、线程也不阻塞了、返回结果也拿到了,但是忽然发现,对方输出的内容是纯字符格式,并不能转成
JSON
方便前端使用,那么你可能需要下面这段代码。
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; public class JsonUtil { /** * 将 [{aaa:123,bbb:456}] 这样的字符串转换为 json 数组 * @param str 需要转换的字符串 */ public static JSONArray str2JSONArray(String str) { JSONArray jsonArray = new JSONArray(); if (str != null) { str = str.replace("{", "{\"") .replace(":", "\":\"") .replace(",", "\",\"") .replace("}", "\"}"); jsonArray = JSON.parseArray(str); } return jsonArray; } }
上面的代码是将字符串转换为了
JSON
数组,如果你需要转换成JSON
对象,则修改fastjson2
的转换函数即可,这里就不做演示了。 - 如果你的程序不死锁了、线程也不阻塞了、返回结果也拿到了,但是忽然发现,对方输出的内容是纯字符格式,并不能转成
-------------------------------------- 独处的时光里,藏着你未来的样子。 --------------------------------------