Java 5 以降の場合はこちらJavaSE ネイティブコマンド実行


コマンドラインの呼び出し

Process process = Runtime.getRuntime().exec(string[]);
int returnCode = process.waitFor();

コマンドラインへの入出力

InputStream stdout = process.getInputStream()
InputStream stderr = process.getErrorStream()
OutputStream stdin = process.getOutputStream()

コマンド実行部品の雛形

 public final class CommandResult
   implements Serializable {
   private String stderr;
   private String stdout;
   private int status;
   private long processingTime;
   
   …(中略:setter/getter)…
 }
 public final class CommandExecutor {
 
   /** コマンド処理が継続中かのフラグ */
   private boolean isRunning = false;
 
   /**
    * シェルコマンドを実行します
    *
    * @param cmdArray コマンド
    * @return 実行結果
    * @throws InterruptedException プロセス実行例外
    * @throws IOException プロセスの出力ストリームが異常
    */
   public CommandResult exec(final String[] cmdArray)
     throws IOException, InterruptedException {
     long startTime = System.currentTimeMillis();
 
     CommandResult res = new CommandResult();
 
     Process process = Runtime.getRuntime().exec(cmdArray);
 
     isRunning = true;
 
     WatchDog wd = new WatchDog(process);
 
     CommandOutputHandler stdoutHandler =
       new CommandOutputHandler(process.getInputStream());
     CommandOutputHandler stderrHandler =
       new CommandOutputHandler(process.getErrorStream());
 
     stdoutHandler.start();
     stderrHandler.start();
     wd.start();
 
     res.setStatus(process.waitFor());
     stdoutHandler.join(); // Solaris だと、join しないと
     stderrHandler.join(); // 高負荷時に stdout、stderr を取りこぼす
     process.destroy(); // Linux だと、destroy しないと高負荷時に Too many open files になる

     long endTime = System.currentTimeMillis();
 
     isRunning = false;
 
     if (!stdoutHandler.isInterrupted()) {
       stdoutHandler.interrupt();
     }
 
     if (!stderrHandler.isInterrupted()) {
       stderrHandler.interrupt();
     }
 
     if (!wd.isInterrupted()) {
       wd.interrupt();
     }
 
     res.setStdout(stdoutHandler.getCmdOutput());
     res.setStderr(stderrHandler.getCmdOutput());
 
     res.setProcessingTime(endTime - startTime);
 
     // ログ出力
     logger.info(cmdId + ":" + res);
 
     return res;
   }
 
   /**
    * ProcessのStdout、Stderrの受け取り処理を行う.
    * <pre>
    * JavaVMの子プロセスからの出力を受け取るバッファは非常に小さいので
    * 出力を別スレッドで逐次取り出す必要がある
    * </pre>
    */
   private final class CommandOutputHandler extends Thread {
     /**
      * シェルコマンドからの出力を格納するバッファ
      */
     private ByteArrayOutputStream buf = new ByteArrayOutputStream();
 
     /**
      * シェルコマンドからの出力を受け取るInputStream
      */
     private InputStream in;
 
     /**
      * デフォルトコンストラクタの無効化
      */
     private CommandOutputHandler() {
       super();
     }
 
     /**
      * コンストラクタ
      * @param argIn シェルコマンドからの出力を受け取るInputStream
      */
     private CommandOutputHandler(final InputStream argIn) {
       super();
       in = argIn;
     }
 
     /**
      * @return cmdOutput を戻します。
      */
     public String getCmdOutput() {
       return buf.toString(); // ByteArrayOutpuStream#toString() は環境変数 LANG でエンコードする
     }
 
     /**
      * シェルコマンドからの出力を読み取ります
      */
     public void run() {
       try {
         byte[] tmp = new byte[128]; // 大きくしすぎるとバッファが詰まる
         int size;
         while((size = in.read) > 0) {
           buf.write(tmp, 0, size);
         }
       } catch (IOException ex) {
         ex = null; // なにもしないし、できない
       } finally {
         try {
           in.close();
         } catch (IOException ex) {
           ex = null; // なにもしないし、できない
         }
       }
     }
   }
 
   /**
    * 起動したシェルコマンドが長い間終了しない場合に強制終了します.
    * <pre>
    * Apache Ant を参考に作成した。
    * </pre>
    */
   private final class WatchDog
     extends Thread {
     /**
      * 実行中のプロセス
      */
     private Process process;
 
     /**
      * Creates a new WatchDog object.
      *
      * @param param 実行予定のProcess
      */
     public WatchDog(final Process param) {
       process = param;
     }
 
     /**
      * デフォルトコンストラクタの無効化
      */
     private WatchDog() {
       super();
     }
 
     /**
      * 30000ミリ秒待ってまだプロセスが動いていれば、プロセスを強制終了します
      */
     public synchronized void run() {
       long until = System.currentTimeMillis() + 30000L
       long now;
 
       while ( isRunning && (until > (now = System.currentTimeMillis()))) {
       
         try {
           wait(until - now);
         } catch (InterruptedException ignoreEx) {
           // Ignore exception
           ignoreEx = null;
         }
       }
 
       if (isRunning) {
         process.destroy();
       }
     }
   }
 }

システムに組み込むには

  1. java.lang.Runtime#exec() は、内部的に fork()->exec() システムコールを使っている.
  2. fork() で、呼び元のプロセス(つまりJavaVMとアプリのメモリ空間) がまるまるコピーされる.
  3. WebコンテナやEJBコンテナ上で使うのは絶対に良くない
    • RMIなどを経由させて使う
      +------------------+        +--------------+
      |EJBアプリ-----------+--RMI-->+コマンド実行アプリ-+---> /bin/sh ls
      |EJBコンテナ           |        |RMIレジストリ     |
      |JavaVM            |        |JavaVM        |
      +------------------+        +--------------+
       EJBはfork()されない         fork()されるのコマンド実行アプリのみ

参考文献

  1. Sun Developer Network (Bug Database) ,http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4227230

Java#JavaSE


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS   sitemap
Last-modified: 2011-04-03 (日) 12:11:38 (2071d)
ISBN10
ISBN13
9784061426061