Apache Commons Execとは
Javaプログラムから外部プロセスを実行する一般的な方法としては、
Commons Execはこのページよりダウンロードできます。本稿執筆時点での最新版はバージョン1.
Commons Execによる外部プロセスの実行
Commons Execを使って外部プロセスを実行する手順は次のようになります。
- CommandLineクラスで実行したいコマンドを作成する
- Executorインスタンスを生成し、
タイムアウトなどの設定を行う - Executorのexecute()メソッドでコマンドを実行する
CommandLineは実行コマンドを表すクラスで、
package jp.gihyo.toolbox.commonsexec; import java.io.IOException; import org.apache.commons.exec.*; public class CommonsExecSample1 { public static void main(String[] args) { // コマンドを作成 CommandLine commandLine = new CommandLine("ping"); commandLine.addArgument("/n"); commandLine.addArgument("5"); commandLine.addArguments("/w 1000"); commandLine.addArgument("127.0.0.1"); // Executorを作成 DefaultExecutor executor = new DefaultExecutor(); try { executor.setExitValue(0); // 正常終了の場合に返される値 // 実行 int exitValue = executor.execute(commandLine); } catch (ExecuteException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } }
実行したいコマンドは次のようなものです。\nはパケットの送信回数を、
> ping /n 5 /w 1000 127.10.0.1
このコマンドをCommandLineのインスタンスとして生成しています。引数をひとつ追加する場合にはaddArgument()を使います。2つ以上追加する場合には、
このCommandLineインスタンスを、
このプログラムを実行すると、
127.0.0.1 に ping を送信しています 32 バイトのデータ: 127.0.0.1 からの応答: バイト数 =32 時間 <1ms TTL=128 127.0.0.1 からの応答: バイト数 =32 時間 <1ms TTL=128 127.0.0.1 からの応答: バイト数 =32 時間 <1ms TTL=128 127.0.0.1 からの応答: バイト数 =32 時間 <1ms TTL=128 127.0.0.1 からの応答: バイト数 =32 時間 <1ms TTL=128 127.0.0.1 の ping 統計: パケット数: 送信 = 5、受信 = 5、損失 = 0 (0% の損失)、 ラウンド トリップの概算時間 (ミリ秒): 最小 = 0ms、最大 = 0ms、平均 = 0ms
CommandLineに対する引数の追加は、
CommandLine commandLine = new CommandLine("ping"); String[] options = {"/n", "5", "/w", "1000"}; commandLine.addArguments(options); commandLine.addArgument("127.0.0.1");
また次に示すようにparse()メソッドを使って、
// コマンドを作成 CommandLine commandLine = CommandLine.parse("ping /n 5 /w 1000 127.0.0.1");
タイムアウト時間の設定
プロセスの実行にはタイムアウトを設定することもできます。その場合、
<import type="text/sourcecode" ref="sources/CommonsExecSample2.txt" caption="リスト3.1" encoding="UTF-8">
pingコマンドの引数に/tを指定した場合、
127.0.0. 1 に ping を送信しています 32 バイトのデータ: 127. 0.0. 1 からの応答: バイト数 =32 時間 <1ms TTL=128 127. 0.0. 1 からの応答: バイト数 =32 時間 <1ms TTL=128 org. apache. commons. exec. ExecuteException: Process exited with an error: 1 (Exitvalue: 1) at org. apache. commons. exec. DefaultExecutor. executeInternal(DefaultExecutor. java:377) at org. apache. commons. exec. DefaultExecutor. execute(DefaultExecutor. java:160) at org. apache. commons. exec. DefaultExecutor. execute(DefaultExecutor. java:147) at jp. gihyo. toolbox. commonsexec. CommonsExecSample2. main(CommonsExecSample2. java:22)
ExecuteExceptionが発生しているのは、
標準出力の内容をファイルに記録する
特に指定しなければ、
// 標準出力の内容をファイルに記録する設定 PumpStreamHandler streamHandler = new PumpStreamHandler(new FileOutputStream("result.log")); executor.setStreamHandler(streamHandler);
出力ストリームだけでなく、
package jp.gihyo.toolbox.commonsexec; import java.io.*; import org.apache.commons.exec.*; public class CommonsExecSample4 { public static void main(String[] args) { // コマンドを作成 CommandLine commandLine = new CommandLine("cmd.exe"); // Executorを作成 DefaultExecutor executor = new DefaultExecutor(); // タイムアウト時間を設定 ExecuteWatchdog watchdog = new ExecuteWatchdog(30000); executor.setWatchdog(watchdog); try { // 標準入力からの入力をプロセスで受け取る PumpStreamHandler streamHandler = new PumpStreamHandler(System.out, System.err, System.in); executor.setStreamHandler(streamHandler); executor.setExitValue(0); // 正常終了の場合に返される値 // 実行 executor.execute(commandLine); System.out.println("Process Exit."); } catch (ExecuteException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } }
PumpStreamHandlerのコンストラクタの第一引数には標準出力、
タイムアウト時間を30秒にしてあるので、
プロセスを非同期で実行する
通常であればexecute()コマンドはプロセスが終了するまでプログラムの処理をブロックするので、
package jp.gihyo.toolbox.commonsexec; import java.io.IOException; import org.apache.commons.exec.*; public class CommonsExecSample5 { public static void main(String[] args) { // コマンドを作成 CommandLine commandLine = CommandLine.parse("ping /n 5 127.0.0.1"); // Executorを作成 DefaultExecutor executor = new DefaultExecutor(); try { executor.setExitValue(0); // 正常終了の場合に返される値 // 非同期モードで実行 DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler(); executor.execute(commandLine, resultHandler); // 並行して実行する処理 System.out.println("Hello!!!!!"); // プロセスの終了待ち resultHandler.waitFor(); System.out.println("Prcess Exit."); } catch (InterruptedException ex) { ex.printStackTrace(); } catch (ExecuteException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } }
実行すると次のような出力結果になります。
Hello!!!!! 127.0.0.1 に ping を送信しています 32 バイトのデータ: 127.0.0.1 からの応答: バイト数 =32 時間 <1ms TTL=128 127.0.0.1 からの応答: バイト数 =32 時間 <1ms TTL=128 127.0.0.1 からの応答: バイト数 =32 時間 <1ms TTL=128 127.0.0.1 からの応答: バイト数 =32 時間 <1ms TTL=128 127.0.0.1 からの応答: バイト数 =32 時間 <1ms TTL=128 127.0.0.1 の ping 統計: パケット数: 送信 = 5、受信 = 5、損失 = 0 (0% の損失)、 ラウンド トリップの概算時間 (ミリ秒): 最小 = 0ms、最大 = 0ms、平均 = 0ms Prcess Exit.
DefaultExecuteResultHandlerはExecuteResultHandlerの実装クラスです。本来であれば
プロセスの終了の終了を待ちたい場所ではExecuteResultHandlerのwaitFor()メソッドを実行します。これによって、
外部プロセスの実行はJavaプログラムの中でも問題を発生させやすい部分として知られています。コードそのものはシンプルですが、