使用Apache Commons Exec管理进程

解河
2023-12-01

网上竟然搜不到详细一点的帖子,估计大家用JAVA去管理进程的场景比较少吧,只好自己总结一个。

Java管理进程,API级别是使用:Runtime.getRuntime().exec(“shell”);这个方法。 
Java在执行命令时输出到某个Buffer里,这个Buffer是有容量限制的,如果满了一直没读取,就会一直等待,造成进程锁死的现象。 
使用Apache Commons Exec,应该可以避免很多类似的坑。 
它提供一些常用的方法用来执行外部进程,另外,它提供了监视狗Watchdog来设监视进程的执行超时,同时也还实现了同步和异步功能, 
Apache Commons Exec涉及到多线程,比如新启动一个进程,Java中需要再开三个线程来处理进程的三个数据流,分别是标准输入,标准输出和错误输出。

1.基本用法

就三步: 
1)创建命令行:CommandLine 
2)创建执行器:DefaultExecutor 
3)用执行器执行命令:executor.execute(cmdLine);

String cmdStr = "ping www.baidu.com -t";
final CommandLine cmdLine = CommandLine.parse(cmdStr);
DefaultExecutor executor = new DefaultExecutor();
int exitValue = executor.execute(cmdLine);

关于这个exitValue: 
一般的进程,其运行结束后,默认值为0。 
对于某些有特殊退出值的程序,需要确定知道其值是什么,然后用executor.setExitValue(退出值);进行明确设置,否则,会出现ExecuteException。 
比如,写一个JAVA程序,在main函数中,用System.exit(10);退出程序,用Exec管理这个进程就必须设置:executor.setExitValue(10); 
如果程序有多个退出值,可使用executor.setExitValues(int[]);函数进行处理。

1.1 通过添加参数方式构建命令。这是官方推荐的方式! 
上面的程序可以改为:

final CommandLine cmdLine = new CommandLine("ping");
cmdLine.addArgument("www.baidu.com");
cmdLine.addArgument("-t");
DefaultExecutor executor = new DefaultExecutor();
int exitValue = executor.execute(cmdLine);

2.超时管理–进程执行时间的管理

设置外部命令执行等待时间,如果超过设置的等待时间,则中断执行。

final CommandLine cmdLine = CommandLine.parse("ping www.baidu.com -t");
ExecuteWatchdog watchdog = new ExecuteWatchdog(5000);//设置超时时间:5秒
DefaultExecutor executor = new DefaultExecutor();
executor.setWatchdog(watchdog);
executor.setExitValue(1);//由于ping被到时间终止,所以其默认退出值已经不是0,而是1,所以要设置它
int exitValue = executor.execute(cmdLine);

3.非阻塞方式执行进程

上面的执行外部命令都是阻塞式,也就是在执行外部命令时,当前线程是阻塞的。 
比如执行ping -t,当成的JAVA程序会一直等着不会停止。 
解决办法:使用DefaultExecuteResultHandler处理外部命令执行的结果,释放当前线程。

final CommandLine cmdLine = CommandLine.parse("ping www.baidu.com -t");
final DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();

DefaultExecutor executor = new DefaultExecutor();
executor.execute(cmdLine, resultHandler);

//这里开始的代码会被立即执行下去,因为上面的语句不会被阻塞。

resultHandler.waitFor(5000);//等待5秒。

可以使用waitFor来阻塞处理逻辑,比如上面的代码,在执行到waitFor时,会等待5秒再继续执行。 
之后,可以通过resultHandler.hasResult()、resultHandler.getExitValue()、resultHandler.getException()获得需要信息。 
注意:getException();得到的是Exec自己的异常,不是应用程序(比如JAVA)代码里面抛出的异常。

4.终止进程

通过Watchdog,可以终止正在运行的进程。

final CommandLine cmdLine = CommandLine.parse("ping www.baidu.com -t");
final ExecuteWatchdog watchdog = new ExecuteWatchdog(Integer.MAX_VALUE);
final DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
DefaultExecutor executor = new DefaultExecutor();

executor.setWatchdog(watchdog);
executor.execute(cmdLine, resultHandler);

Thread.sleep(10000);//等进程执行一会,再终止它
System.out.println("--> Watchdog is watching ? " + watchdog.isWatching());
watchdog.destroyProcess();//终止进程
System.out.println("--> destroyProcess done.");
System.out.println("--> Watchdog is watching ? " + watchdog.isWatching());
System.out.println("--> Watchdog should have killed the process : " + watchdog.killedProcess());
System.out.println("--> wait result is : " + resultHandler.hasResult());
System.out.println("--> exit value is : " + resultHandler.getExitValue());
System.out.println("--> exception is : " + resultHandler.getException());

resultHandler.waitFor(5000);//等待5秒。下面加上上面的几个System.out,看看进程状态是什么。

5.获得进程的输出信息

可以在程序中,通过PumpStreamHandler,截获进程的各种输出,包括output 和 error stream。

String cmdStr = "ping www.baidu.com";
final CommandLine cmdLine = CommandLine.parse(cmdStr);

DefaultExecutor executor = new DefaultExecutor();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
executor.setStreamHandler(new PumpStreamHandler(baos, baos));

executor.setExitValue(1);
int exitValue = executor.execute(cmdLine);

final String result = baos.toString().trim();
System.out.println(result);//这个result就是ping输出的结果。如果是JAVA程序,抛出了异常,也被它获取。
 类似资料: