9

Apache Commons Exec ライブラリを使用して、外部コマンド ライン アプリケーションを使用する必要がある Java アプリケーションを作成しています。実行する必要があるアプリケーションの読み込み時間がかなり長いため、毎回新しいプロセスを作成するのではなく、1 つのインスタンスを維持することが望ましいでしょう。アプリケーションの仕組みは非常にシンプルです。開始すると、新しい入力を待機し、出力としてデータを生成します。どちらもアプリケーションの標準 I/O を使用します。

つまり、CommandLine を実行してから、PumpStreamHandler を 3 つの別個のストリーム (出力、エラー、および入力) で使用し、それらのストリームを使用してアプリケーションと対話するという考えになります。これまでのところ、1 つの入力と 1 つの出力があり、その後アプリケーションがシャットダウンするという基本的なシナリオでこの作業を行いました。しかし、2 番目のトランザクションを実行しようとするとすぐに、問題が発生します。

CommandLine を作成した後、Executor を作成し、次のように起動します。

this.executor = new DefaultExecutor();

PipedOutputStream stdout = new PipedOutputStream();
PipedOutputStream stderr = new PipedOutputStream();
PipedInputStream stdin = new PipedInputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(stdout, stderr, stdin);

this.executor.setStreamHandler(streamHandler);

this.processOutput = new BufferedInputStream(new PipedInputStream(stdout));
this.processError = new BufferedInputStream(new PipedInputStream(stderr));
this.processInput = new BufferedOutputStream(new PipedOutputStream(stdin));

this.resultHandler = new DefaultExecuteResultHandler();
this.executor.execute(cmdLine, resultHandler);

次に、3 つの異なるスレッドを起動し、それぞれが異なるストリームを処理します。入力と出力を処理する 3 つの SynchronousQueues もあります (1 つは入力ストリームの入力として使用され、1 つは新しいコマンドが起動されたことを outputQueue に通知するため、もう 1 つは出力用です)。たとえば、入力ストリーム スレッドは次のようになります。

while (!killThreads) {
    String input = inputQueue.take();

    processInput.write(input.getBytes());
    processInput.flush();

    IOQueue.put(input);
}

while ループを削除してこれを 1 回だけ実行すると、すべてが完全に機能するように見えます。明らかに、もう一度実行しようとすると、PumpStreamHandler は 2 つの異なるスレッドからアクセスされているため、例外をスローします。

ここでの問題は、スレッドが終了するまで processInput が真にフラッシュされないように見えることです。デバッグ時に、コマンド ライン アプリケーションは、スレッドが終了して初めて実際に入力を受け取りますが、while ループが保持されている場合は入力を受け取りません。processInput をフラッシュするためにさまざまなことを試しましたが、何も機能していないようです。

誰かが以前に似たようなことを試みたことがありますか? 不足しているものはありますか?どんな助けでも大歓迎です!

4

3 に答える 3

10

私は最終的にこれを機能させる方法を考え出しました。Commons Exec ライブラリのコードを調べたところ、PumpStreamHandler によって使用される StreamPumpers が、新しいデータが着信するたびにフラッシュされないことに気付きました。これが、ストリームを自動的にフラッシュして閉じたため、一度だけ実行したときにコードが機能した理由です。そこで、AutoFlushingStreamPumper と AutoFlushingPumpStreamHandler という名前のクラスを作成しました。後者は通常の PumpStreamHandler と同じですが、通常のものの代わりに AutoFlushingStreamPumpers を使用します。AutoFlushingStreamPumper は標準の StreamPumper と同じことを行いますが、何かを書き込むたびに出力ストリームをフラッシュします。

私はそれをかなり広範囲にテストしましたが、うまくいくようです。これを理解しようとしたすべての人に感謝します!

于 2011-09-23T15:50:40.873 に答える
2

私の目的では、「ExecuteStreamHandler」をオーバーライドするだけでよいことがわかりました。stderr を StringBuilder にキャプチャし、stdin にストリーミングして stdout から受信できるようにする私のソリューションを次に示します。

class SendReceiveStreamHandler implements ExecuteStreamHandler

クラス全体を GitHub の要旨としてここで見ることができます。

于 2012-08-20T19:31:04.853 に答える
1

プロセスの STDIN に複数のコマンドを記述できるようにするために、新しいコマンドを作成しました。

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Map;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.lang3.CharEncoding;

public class ProcessExecutor extends DefaultExecutor {

    private BufferedWriter processStdinput;

    @Override
    protected Process launch(CommandLine command, Map env, File dir) throws IOException {
        Process process = super.launch(command, env, dir);
        processStdinput = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), CharEncoding.UTF_8));
        return process;
    }

    /**
     * Write a line in the stdin of the process.
     * 
     * @param line
     *            does not need to contain the carriage return character.
     * @throws IOException
     *             in case of error when writing.
     * @throws IllegalStateException
     *             if the process was not launched.
     */
    public void writeLine(String line) throws IOException {
        if (processStdinput != null) {
            processStdinput.write(line);
            processStdinput.newLine();
            processStdinput.flush();
        } else {
            throw new IllegalStateException();
        }
    }

}

この新しい Executor を使用するには、PumpStreamHandler 内にパイプされたストリームを保持して、STDIN が PumpStreamHandler によって閉じられるのを回避します。

ProcessExecutor executor = new ProcessExecutor();
executor.setExitValue(0);
executor.setWorkingDirectory(workingDirectory);
executor.setWatchdog(new ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT));
executor.setStreamHandler(new PumpStreamHandler(outHanlder, outHanlder, new PipedInputStream(new PipedOutputStream())));
executor.execute(commandLine, this);

エグゼキュータの writeLine() メソッドを使用するか、独自のメソッドを作成できます。

于 2014-05-20T18:44:11.570 に答える