Java管理进程,API级别是使用:Runtime.getRuntime().exec(“shell”);这个方法。
Java在执行命令时输出到某个Buffer里,这个Buffer是有容量限制的,如果满了一直没读取,就会一直等待,造成进程锁死的现象。
使用Apache Commons Exec,应该可以避免很多类似的坑。
它提供一些常用的方法用来执行外部进程,另外,它提供了监视狗Watchdog来设监视进程的执行超时,同时也还实现了同步和异步功能,
Apache Commons Exec涉及到多线程,比如新启动一个进程,Java中需要再开三个线程来处理进程的三个数据流,分别是标准输入,标准输出和错误输出。
maven依赖
org.apache.commons
commons-exec
1.3
工具类ScriptUtil
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.PumpStreamHandler;
import org.springframework.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 1、内嵌编译器如"PythonInterpreter"无法引用扩展包,因此推荐使用java调用控制台进程方式"Runtime.getRuntime().execCmd()"来运行脚本(shell或python);
* 2、因为通过java调用控制台进程方式实现,需要保证目标机器PATH路径正确配置对应编译器;
* 3、暂时脚本执行日志只能在脚本执行结束后一次性获取,无法保证实时性;因此为确保日志实时性,可改为将脚本打印的日志存储在指定的日志文件上;
* 4、python 异常输出优先级高于标准输出,体现在Log文件中,因此推荐通过logging方式打日志保持和异常信息一致;否则用prinf日志顺序会错乱
*
*
* @author lism
* @date 2018年11月8日10:02:30
*/
public class ScriptUtil {
/**
* make script file
*
* @param scriptFileName
* @param content
* @throws IOException
*/
public static void markScriptFile(String scriptFileName, String content) throws Exception {
// make file, filePath/gluesource/666-123456789.py
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(scriptFileName);
fileOutputStream.write(content.getBytes("UTF-8"));
fileOutputStream.close();
} catch (Exception e) {
throw e;
} finally {
if (fileOutputStream != null) {
fileOutputStream.close();
}
}
}
/**
* 日志文件输出方式
*
* 优点:支持将目标数据实时输出到指定日志文件中去
* 缺点:
* 标准输出和错误输出优先级固定,可能和脚本中顺序不一致
* Java无法实时获取
*
* @param command
* @param scriptFile
* @param logFile
* @param params
* @return
* @throws IOException
*/
public static int execToFile(String command, String scriptFile, String logFile, String... params) throws IOException {
// 标准输出:print (null if watchdog timeout)
// 错误输出:logging + 异常 (still exists if watchdog timeout)
// 标准输入
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(logFile, true);
PumpStreamHandler streamHandler = new PumpStreamHandler(fileOutputStream, fileOutputStream, null);
int exitValue = execCmd(command, scriptFile, params, streamHandler);
return exitValue;
} catch (Exception e) {
return -1;
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
}
}
}
}
public static int execCmd(String command, String scriptFile, String... params) throws IOException {
PumpStreamHandler streamHandler = new PumpStreamHandler(new CollectingLogOutputStream());
// command
return execCmd(command, scriptFile, params, streamHandler);
}
public static int execCmd(String command, String scriptFile, String[] params, PumpStreamHandler streamHandler) throws IOException {
CommandLine commandline = new CommandLine(command);
if(!StringUtils.isEmpty(scriptFile)){
commandline.addArgument(scriptFile);
}
if (params != null && params.length > 0) {
commandline.addArguments(params);
}
// execCmd
DefaultExecutor exec = new DefaultExecutor();
exec.setExitValues(null);
exec.setStreamHandler(streamHandler);
int exitValue = exec.execute(commandline);// exit code: 0=success, 1=error
return exitValue;
}
public static String execToString(String command, String scriptFile, String logFile, String... params) throws IOException {
// 标准输出:print (null if watchdog timeout)
// 错误输出:logging + 异常 (still exists if watchdog timeout)
// 标准输入
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream, outputStream);
execCmd(command, scriptFile, params, streamHandler);
} catch (Exception e) {
return null;
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
}
}
}
return outputStream.toString("gbk");
}
}
如果不是要把脚本的日志输出到文件或者流中,而是实时处理每一行输出,需要实现LogOutputStream,类似下面的实现类CollectingLogOutputStream
import org.apache.commons.exec.LogOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedList;
import java.util.List;
/**
* @author lism
*/
public class CollectingLogOutputStream extends LogOutputStream {
private static Logger logger = LoggerFactory.getLogger(CollectingLogOutputStream.class);
private final List lines = new LinkedList();
@Override protected void processLine(String line, int level) {
lines.add(line);
logger.info("日志级别{}:{}",level,line);
}
public List getLines() {
return lines;
}
}
使用方式是将CollectingLogOutputStream实现类传给PumpStreamHandler当参数,这样就能实时处理脚本输出了
public static int execCmd(String command, String scriptFile, String... params) throws IOException {
PumpStreamHandler streamHandler = new PumpStreamHandler(new CollectingLogOutputStream());
// command
return execCmd(command, scriptFile, params, streamHandler);
}