Die Integration externer Programme in Java-Anwendungen ist eine häufige Notwendigkeit, sei es für die Automatisierung von Aufgaben, die Interaktion mit Systemressourcen oder die Kommunikation mit anderen Anwendungen. Die ProcessBuilder-Klasse in Java bietet eine flexible Möglichkeit, externe Prozesse zu starten und mit ihnen zu interagieren. In diesem Artikel werden wir den Umgang mit ProcessBuilder sowie verwandten Konzepten wie Umgebungsvariablen, Kommandozeilenparametern, stdin/stdout und möglichen Herausforderungen beim Lesen von Prozessausgaben besprechen.

1. Prozesse starten mit ProcessBuilder

Die ProcessBuilder-Klasse in Java ermöglicht es, externe Prozesse zu starten und mit ihnen zu interagieren. Um einen einfachen Prozess zu starten, können Sie ProcessBuilder wie folgt verwenden:

import java.io.IOException;

public class ExternalProcessExample {
    public static void main(String[] args) {
        try {
            ProcessBuilder processBuilder = new ProcessBuilder("command", "arg1", "arg2");
            Process process = processBuilder.start();

            // Warten, bis der Prozess beendet ist
            int exitCode = process.waitFor();

            System.out.println("Prozess beendet mit Exit-Code: " + exitCode);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}Code-Sprache: JavaScript (javascript)

Hier wird ein externer Prozess mit einem Befehl („command“) und optionalen Argumenten („arg1“, „arg2“) gestartet. Der waitFor-Aufruf blockiert, bis der gestartete Prozess beendet ist, und gibt den Exit-Code des Prozesses zurück.

2. Umgebungsvariablen und Kommandozeilenparameter setzen

Oftmals ist es erforderlich, Umgebungsvariablen oder Kommandozeilenparameter für den gestarteten Prozess festzulegen. Dies kann über die environment()-Methode von ProcessBuilder erfolgen:

import java.io.IOException;

public class ProcessWithEnvironment {
    public static void main(String[] args) {
        try {
            ProcessBuilder processBuilder = new ProcessBuilder("command", "arg1", "arg2");

            // Umgebungsvariablen setzen
            processBuilder.environment().put("KEY", "VALUE");

            Process process = processBuilder.start();

            int exitCode = process.waitFor();

            System.out.println("Prozess beendet mit Exit-Code: " + exitCode);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}Code-Sprache: JavaScript (javascript)

3. stdin und stdout steuern

Die ProcessBuilder-Klasse ermöglicht auch die Steuerung von stdin und das Abfangen von stdout des gestarteten Prozesses. Dazu können Sie getOutputStream() für die Eingabe und getInputStream() für die Ausgabe verwenden:

import java.io.*;

public class ProcessWithInputOutput {
    public static void main(String[] args) {
        try {
            ProcessBuilder processBuilder = new ProcessBuilder("command");

            // stdin des Prozesses abrufen
            OutputStream stdin = processBuilder.start().getOutputStream();

            // Daten in stdin schreiben
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));
            writer.write("Input-Daten");
            writer.close();

            // stdout des Prozesses abrufen
            InputStream stdout = processBuilder.start().getInputStream();

            // Daten von stdout lesen
            BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
            String output = reader.readLine();

            System.out.println("Prozessausgabe: " + output);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}Code-Sprache: JavaScript (javascript)

4. Verwendung der StreamGobbler-Hilfsklasse

Um stdout und stderr des gestarteten Prozesses effizienter zu verarbeiten, ist es ratsam, eine Hilfsklasse wie StreamGobbler zu verwenden. Diese Klasse liest die Ausgaben des Prozesses in einem separaten Thread und verhindert, dass die Puffer des Betriebssystems überlaufen.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class StreamGobbler extends Thread {
    private InputStream inputStream;
    private Consumer<String> consumer;

    public StreamGobbler(InputStream inputStream, Consumer<String> consumer) {
        this.inputStream = inputStream;
        this.consumer = consumer;
    }

    @Override
    public void run() {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
            reader.lines().forEach(consumer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}Code-Sprache: JavaScript (javascript)

Die StreamGobbler-Klasse erfasst den InputStream des Prozesses und leitet die Ausgaben an den angegebenen Consumer weiter. Dadurch können Sie die Ausgaben des Prozesses nach Bedarf verarbeiten, z.B. sie in einer Datei speichern oder analysieren.

5. Warnung vor Blockaden bei stdout

Beim Lesen von stdout ist es wichtig, die Ausgaben des Prozesses kontinuierlich zu verarbeiten, um zu verhindern, dass die Puffer des Betriebssystems überlaufen und der Prozess blockiert. Dies kann erreicht werden, indem Sie den StreamGobbler oder einen ähnlichen Mechanismus verwenden, um die Ausgaben parallel zum Hauptprogramm zu verarbeiten – oder komplett zu ignorieren (denn die Hauptsache ist, dass der Stream ausgelesen wird)!

Insgesamt bietet die ProcessBuilder-Klasse in Java eine leistungsstarke Möglichkeit, mit externen Prozessen zu interagieren. Mit den oben genannten Konzepten können Sie nicht nur Prozesse starten, sondern auch Umgebungsvariablen setzen, Kommandozeilenparameter übergeben und die stdin/stdout-Streams effizient steuern. Die Verwendung von Hilfsklassen wie StreamGobbler erleichtert zudem die Handhabung von Prozessausgaben und minimiert das Risiko von Blockaden durch volle Puffer.