Apache Commons Execとは
Javaプログラムから外部プロセスを実行する一般的な方法としては,標準ライブラリに用意されているjava.lang.ProcessBuilderクラスや,java.lang.Runtime.exec()メソッドがあります。しかしこれらのクラス/メソッドによるサポートは限定的であり,あまり使い勝手が良くないことでも知られています。「Apache Commons Exec」(以下,Commons Exec)は,そのような標準的な方法に変わる外部プロセスの起動手段を提供してくれるオープンソースのライブラリです。特にプロセスに対する適切な入出力処理が,比較的簡単に記述できるようになっている点が大きなメリットです。
Commons Execはこのページよりダウンロードできます。本稿執筆時点での最新版はバージョン1.1です。ダウンロードしたファイルを解凍し,中に含まれている「commons-exec-1.1.jar」をクラスパスに追加して利用します。
Commons Execによる外部プロセスの実行
Commons Execを使って外部プロセスを実行する手順は次のようになります。
- CommandLineクラスで実行したいコマンドを作成する
- Executorインスタンスを生成し,タイムアウトなどの設定を行う
- Executorのexecute()メソッドでコマンドを実行する
CommandLineは実行コマンドを表すクラスで,実行したいコマンドや実行ファイル名をコンストラクタに渡してインスタンスを生成します。コマンドに渡す実行時引数はaddArgument()メソッドやaddArguments()メソッドで設定します。Executorはプロセスの実行や環境変数の設定などを行うための機能が定義されたインターフェースで,実装クラスとしてはDefaultExecutorクラスが用意されています。次のコードは,Commons Execを使ってWindows上でpingコマンドを実行するプログラムの例です。
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はパケットの送信回数を,\wはタイムアウト時間を指定する引数です。
> ping /n 5 /w 1000 127.10.0.1
このコマンドをCommandLineのインスタンスとして生成しています。引数をひとつ追加する場合にはaddArgument()を使います。2つ以上追加する場合には,addArguments()にスペースで区切って渡します。
このCommandLineインスタンスを,Executorのexecute()メソッドに渡して呼び出せば,対象のコマンドがサブプロセスとして実行されます。デフォルトでは,execute()メソッドはプロセスが終了するまで処理をブロックします。execute()の戻り値は,プロセスが終了時に返す終了コードです。setExitValue()は,どの終了コードが返された場合に正常終了とみなすかを設定するためのメソッドになっています。ここで設定した値以外が返される場合には,ExecutorはExecuteExceptionを発生させます。
このプログラムを実行すると,次のようにpingコマンドで5回のパケットを送信し,その結果が標準出力に出力されることが確認できます。
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 =
CommandLine.parse("ping /n 5 /w 1000 127.0.0.1");
タイムアウト時間の設定
プロセスの実行にはタイムアウトを設定することもできます。その場合,設定された時間が経過すると,強制的にプロセスは終了されます。タイムアウトは,プロセスの実行時間を管理するExecuteWatchdogクラスを使って設定します。このクラスのコンストラクタにタイムアウト時間を指定してインスタンスを生成し,それを次のようにsetWatchdog()メソッドでExecutorにセットします。
<import type="text/sourcecode" ref="sources/CommonsExecSample2.txt" caption="リスト3.1" encoding="UTF-8">
pingコマンドの引数に/tを指定した場合,プロセスが停止されるまでパケットを送り続けることになります。実行すると,2秒経過した時点でプロセスが強制的に終了されるため,次のような結果になります。
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が発生しているのは,プロセスの終了コードとして,正常終了として設定した0ではなく1が返されたためです。ExecuteWatchdogによるタイムアウトで強制終了されたので,正常時とは異なる終了コードになったわけです。

