This is the sixth article in a series. For the first few, please click the link
Good News for programmers – An introduction to Apache Commons
Good news for Programmers – Apache Commons Lang
Programmer’s Gospel – Apache Commons IO
Good news for programmers – Apache Commons Codec
Programmer’s Gospel – Apache Commons Compress
Apache Commons Exec is primarily used to execute commands for external processes. The latest version of Exec is currently 1.3, and the minimum requirement is Java5.
It is also a common requirement to execute external process commands in Java, which is operating system-specific and requires understanding of system-specific behavior, such as using cmd.exe on Windows. To reliably execute an external process, you also need to process environment variables before or after executing commands.
Apache Commons Exec is designed to address the various issues outlined above. And the code is relatively simple to implement.
Maven coordinates are as follows:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.3</version>
</dependency>
Copy the code
Here’s a brief introduction to how to use it.
01. Synchronous invocation
Synchronous invocation of a system command blocks the current thread until a result is obtained.
1. Write in JDK
// Do not use the utility class
Process process = Runtime.getRuntime().exec("Ping CMD/c 192.168.1.10." ");
int exitCode = process.waitFor(); // Block until complete
if (exitCode == 0) { // If the status code is 0, the command is executed successfully
String result = IOUtils.toString(process.getInputStream()); // "IOUtils" Commons IO utility class, see the previous sequel for details
System.out.println(result);
} else {
String errMsg = IOUtils.toString(process.getErrorStream());
System.out.println(errMsg);
}
Copy the code
Wait, there’s a downside to that. If the execution of an installation script produces a large amount of output on the console, the process may stall.
This is because the buffer is full and cannot write data, causing the thread to block. External phenomenon is that the process cannot stop, it does not occupy resources, and there is no reaction.
In this case, it is possible to start a separate thread to read the input stream and avoid filling up the buffer, as shown in the following example:
final Process process = Runtime.getRuntime().exec("Ping CMD/c 192.168.1.10." ");
new Thread(() -> {
try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while((line = br.readLine()) ! =null) {
try {
process.exitValue();
break; // exitValue indicates that the process is finished and exits the loop
} catch (IllegalThreadStateException e) {
// An exception indicates that the process has not finished executing
}
// Only output is done here. If you have other requirements for the results, you can use another container to collect the output on the main threadSystem.out.println(line); }}catch (IOException e) {
throw new RuntimeException(e);
}
}).start();
process.waitFor();
Copy the code
Process.geterrorstream () is handled if too much exception information is printed.
2. Use Commons
Commons-exec commands no longer need to consider the execution environment. For example, Windows does not need to prefix “CMD /c”. You can use custom streams to accept results, such as file streams to save results to a file, network streams to save results to a remote server, and so on. The following example uses a byte stream to receive it directly for simplicity (don’t use a byte stream if the result is very large, as it is prone to overflow).
String command = "Ping 192.168.1.10." ";
// Receive the normal result stream
ByteArrayOutputStream susStream = new ByteArrayOutputStream();
// Receive the abnormal result stream
ByteArrayOutputStream errStream = new ByteArrayOutputStream();
CommandLine commandLine = CommandLine.parse(command);
DefaultExecutor exec = new DefaultExecutor();
PumpStreamHandler streamHandler = new PumpStreamHandler(susStream, errStream);
exec.setStreamHandler(streamHandler);
int code = exec.execute(commandLine);
System.out.println("result code: " + code);
// Different operating systems pay attention to the encoding, otherwise the result is garbled
String suc = susStream.toString("GBK");
String err = errStream.toString("GBK");
System.out.println(suc);
System.out.println(err);
Copy the code
02. Asynchronous invocation
1. Write in JDK
The Runtime API does not support asynchronous execution. To get the result asynchronously, you need to create your own thread to continuously poll the process state and notify the main thread. The example is so simple that many of the details are not rigorous, but just the general idea (more code is needed to implement the exec convenient API).
public class RuntimeAsyncDemo {
public static void main(String[] args) throws Exception {
System.out.println("1. Start execution");
String cmd = "Ping 192.168.1.11 CMD/c"; // Assume a time consuming operation
execAsync(cmd, processResult -> {
System.out.println("3. Asynchronous execution completed, success=" + processResult.success + "; msg=" + processResult.result);
System.exit(0);
});
// Do something else... .
System.out.println("2. Do other operations");
// Prevent the main thread from exiting, causing the program to exit
Thread.currentThread().join();
}
private static void execAsync(String command, Consumer<ProcessResult> callback) throws IOException {
final Process process = Runtime.getRuntime().exec(command);
new Thread(() -> {
StringBuilder successMsg = new StringBuilder();
try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK"))) {
// Store the provisional results
String line;
while((line = br.readLine()) ! =null) {
try {
successMsg.append(line).append("\r\n");
int exitCode = process.exitValue();
ProcessResult pr = new ProcessResult();
if (exitCode == 0) {
pr.success = true;
pr.result = successMsg.toString();
} else {
pr.success = false;
pr.result = IOUtils.toString(process.getErrorStream());
}
callback.accept(pr); // Callback to the function registered by the main thread
break; // exitValue indicates that the process is finished and exits the loop
} catch (IllegalThreadStateException e) {
// An exception indicates that the process has not finished executing
}
try {
// Wait for 100 milliseconds before checking for completion
Thread.sleep(100);
} catch(InterruptedException e) { e.printStackTrace(); }}}catch (IOException e) {
throw new RuntimeException(e);
}
}).start();
}
private static class ProcessResult {
booleansuccess; String result; }}Copy the code
2. Use Commons
Commons-exec supports asynchronous calls natively, so let’s take a look at the example below.
@Test
public void execAsync(a) throws IOException, InterruptedException {
String command = "Ping 192.168.1.10." ";
// Receive the normal result stream
ByteArrayOutputStream susStream = new ByteArrayOutputStream();
// Receive the abnormal result stream
ByteArrayOutputStream errStream = new ByteArrayOutputStream();
CommandLine commandLine = CommandLine.parse(command);
DefaultExecutor exec = new DefaultExecutor();
PumpStreamHandler streamHandler = new PumpStreamHandler(susStream, errStream);
exec.setStreamHandler(streamHandler);
ExecuteResultHandler erh = new ExecuteResultHandler() {
@Override
public void onProcessComplete(int exitValue) {
try {
String suc = susStream.toString("GBK");
System.out.println(suc);
System.out.println("3. Asynchronous execution completed");
} catch(UnsupportedEncodingException uee) { uee.printStackTrace(); }}@Override
public void onProcessFailed(ExecuteException e) {
try {
String err = errStream.toString("GBK");
System.out.println(err);
System.out.println("3. Asynchronous execution error");
} catch(UnsupportedEncodingException uee) { uee.printStackTrace(); }}}; System.out.println("1. Start execution");
exec.execute(commandLine, erh);
System.out.println("2. Do other operations");
// Prevent the main thread from exiting, causing the program to exit
Thread.currentThread().join();
}
Copy the code
3. Monitor
Commons-exec allows you to monitor the execution status of external processes and perform operations such as timeout, stopping, and so on.
When you use Runtime.geTruntime ().exec(CMD) to execute some system commands, such as the MOUNT of NFS share, the process will be blocked due to NFS service exceptions and other reasons, so that the program cannot execute further, and the exception cannot be caught, which is equivalent to stuck. In this case, it would be nice if there was a timeout waiver function. Of course, the timeout function can be implemented by polling process.exitValue(), which is a little more complicated, so I won’t do an example here.
Commons-exec handles timeouts primarily through the ExecuteWatchdog class. Here’s an example
String command = "Ping 192.168.1.10." ";
ByteArrayOutputStream susStream = new ByteArrayOutputStream();
ByteArrayOutputStream errStream = new ByteArrayOutputStream();
CommandLine commandLine = CommandLine.parse(command);
DefaultExecutor exec = new DefaultExecutor();
// Set the one-minute timeout
ExecuteWatchdog watchdog = new ExecuteWatchdog(60*1000);
exec.setWatchdog(watchdog);
PumpStreamHandler streamHandler = new PumpStreamHandler(susStream, errStream);
exec.setStreamHandler(streamHandler);
try {
int code = exec.execute(commandLine);
System.out.println("result code: " + code);
// Different operating systems pay attention to the encoding, otherwise the result is garbled
String suc = susStream.toString("GBK");
String err = errStream.toString("GBK");
System.out.println(suc+err);
} catch (ExecuteException e) {
if (watchdog.killedProcess()) {
// Was deliberately killed by the watchdog
System.err.println("Out of time."); }}Copy the code
ExecuteWatchdog also supports destroying processes by calling destroyProcess(), and because ExecuteWatchdog executes asynchronously, it doesn’t stop immediately. It’s also relatively easy to use and I won’t explain it.
04. Summary
Commons-exec is a great alternative to the JDK’s Runtime API by masking different operating system commands, resolving Runtime buffer issues that cause threads to get stuck, and supporting timeouts and so on.
I look forward to your attention as I continue to introduce you to other useful utility libraries in The Commons section.