Gids voor java.lang.ProcessBuilder API

1. Overzicht

De Process API biedt een krachtige manier om commando's van het besturingssysteem in Java uit te voeren. Het heeft echter verschillende opties die het omslachtig kunnen maken om mee te werken.

In deze tutorial we zullen bekijken hoe Java dat verlicht met de ProcessBuilder API.

2. ProcessBuilder API

De ProcessBuilder class biedt methoden voor het maken en configureren van besturingssysteemprocessen. Elk ProcessBuilder instantie stelt ons in staat om een ​​verzameling procesattributen te beheren. We kunnen dan een nieuwe beginnen Werkwijze met die gegeven attributen.

Hier zijn een paar veelvoorkomende scenario's waarin we deze API zouden kunnen gebruiken:

  • Zoek de huidige Java-versie
  • Stel een aangepaste sleutelwaarde-kaart op voor onze omgeving
  • Verander de werkmap waar onze shell-opdracht wordt uitgevoerd
  • Leid invoer- en uitvoerstromen om naar aangepaste vervangingen
  • Neem beide streams van het huidige JVM-proces over
  • Voer een shell-commando uit vanuit Java-code

In latere secties zullen we voor elk van deze praktische voorbeelden bekijken.

Maar laten we, voordat we in de werkende code duiken, eens kijken naar wat voor soort functionaliteit deze API biedt.

2.1. Methode Samenvatting

In deze sectie we gaan een stap terug doen en kort kijken naar de belangrijkste methoden in het ProcessBuilder klasse. Dit zal ons helpen als we later in enkele echte voorbeelden duiken:

  • ProcessBuilder (String ... opdracht)

    Om een ​​nieuwe procesbouwer te maken met het opgegeven besturingssysteemprogramma en argumenten, kunnen we deze handige constructor gebruiken.

  • directory (bestandsmap)

    We kunnen de standaard werkmap van het huidige proces overschrijven door de directory methode en het doorgeven van een het dossier voorwerp. Standaard is de huidige werkmap ingesteld op de waarde die wordt geretourneerd door de gebruiker.dir systeemeigenschap.

  • milieu()

    Als we de huidige omgevingsvariabelen willen krijgen, kunnen we eenvoudig de milieu methode. Het geeft ons een kopie van de huidige procesomgeving met behulp van System.getenv () maar als een Kaart .

  • erfelijkheid ()

    Als we willen specificeren dat de bron en bestemming voor ons subproces standaard I / O hetzelfde moeten zijn als die van het huidige Java-proces, kunnen we de erfelijkheid methode.

  • redirectInput (bestandsbestand), redirectOutput (bestandsbestand), redirectError (bestandsbestand)

    Als we de standaard invoer, uitvoer en foutbestemming van de procesbouwer willen omleiden naar een bestand, hebben we deze drie vergelijkbare omleidingsmethoden tot onze beschikking.

  • begin()

    Last but not least, om een ​​nieuw proces te starten met wat we hebben geconfigureerd, bellen we gewoon begin().

We moeten opmerken dat deze klasse NIET is gesynchroniseerd. Als we bijvoorbeeld meerdere threads hebben die toegang hebben tot een ProcessBuilder instantie gelijktijdig dan moet de synchronisatie extern worden beheerd.

3. Voorbeelden

Nu we een basiskennis hebben van de ProcessBuilder API, laten we enkele voorbeelden doorlopen.

3.1. Gebruik makend van ProcessBuilder om de versie van Java af te drukken

In dit eerste voorbeeld zullen we het Java commando met één argument om de versie te krijgen.

Procesproces = nieuwe ProcessBuilder ("java", "-version"). Start ();

Eerst creëren we onze ProcessBuilder object doorgeven van de opdracht- en argumentwaarden aan de constructor. Vervolgens starten we het proces met behulp van de begin() methode om een Werkwijze voorwerp.

Laten we nu eens kijken hoe we met de uitvoer moeten omgaan:

Lijstresultaten = readOutput (process.getInputStream ()); assertThat ("Resultaten mogen niet leeg zijn", results, is (not (empty ()))); assertThat ("Resultaten moeten java-versie bevatten:", results, hasItem (containsString ("java-versie"))); int exitCode = process.waitFor (); assertEquals ("Er mogen geen fouten worden gedetecteerd", 0, exitCode);

Hier lezen we de procesoutput en controleren of de inhoud is die we verwachten. In de laatste stap wachten we tot het proces klaar is met gebruiken process.waitFor ().

Zodra het proces is voltooid, vertelt de geretourneerde waarde ons of het proces succesvol was of niet.

Enkele belangrijke punten om in gedachten te houden:

  • De argumenten moeten in de juiste volgorde staan
  • Bovendien worden in dit voorbeeld de standaard werkdirectory en omgeving gebruikt
  • We bellen bewust niet process.waitFor () tot nadat we de uitvoer hebben gelezen, omdat de uitvoerbuffer het proces kan vertragen
  • We hebben de aanname gemaakt dat de Java commando is beschikbaar via de PAD variabele

3.2. Een proces starten met een gewijzigde omgeving

In dit volgende voorbeeld gaan we zien hoe we de werkomgeving kunnen aanpassen.

Maar voordat we dat doen, laten we eerst eens kijken naar het soort informatie dat we in de standaardomgeving kunnen vinden:

ProcessBuilder processBuilder = nieuwe ProcessBuilder (); Kaartomgeving = processBuilder.environment (); environment.forEach ((sleutel, waarde) -> System.out.println (sleutel + waarde));

Dit drukt eenvoudig elk van de variabelegegevens af die standaard worden verstrekt:

PAD / usr / bin: / bin: / usr / sbin: / sbin SHELL / bin / bash ...

Nu gaan we een nieuwe omgevingsvariabele toevoegen aan onze ProcessBuilder object en voer een commando uit om de waarde ervan uit te voeren:

environment.put ("GREETING", "Hola Mundo"); processBuilder.command ("/ bin / bash", "-c", "echo $ GREETING"); Proces process = processBuilder.start ();

Laten we de stappen ontleden om te begrijpen wat we hebben gedaan:

  • Voeg een variabele met de naam 'GREETING' met de waarde 'Hola Mundo' toe aan onze omgeving, wat een standaard is Kaart
  • Deze keer, in plaats van de constructor te gebruiken, stellen we het commando en de argumenten in via de commando (String ... commando) methode rechtstreeks.
  • We starten dan ons proces volgens het vorige voorbeeld.

Om het voorbeeld te voltooien, controleren we of de uitvoer onze begroeting bevat:

Lijstresultaten = readOutput (process.getInputStream ()); assertThat ("Resultaten mogen niet leeg zijn", results, is (not (empty ()))); assertThat ("Resultaten moeten java-versie bevatten:", results, hasItem (containsString ("Hola Mundo")));

3.3. Een proces starten met een gewijzigde werkdirectory

Soms kan het handig zijn om de werkdirectory te wijzigen. In ons volgende voorbeeld gaan we zien hoe we dat moeten doen:

@Test openbare ongeldig gegevenProcessBuilder_whenModifyWorkingDir_thenSuccess () gooit IOException, InterruptedException {ProcessBuilder processBuilder = nieuwe ProcessBuilder ("/ bin / sh", "-c", "ls"); processBuilder.directory (nieuw bestand ("src")); Proces process = processBuilder.start (); Lijstresultaten = readOutput (process.getInputStream ()); assertThat ("Resultaten mogen niet leeg zijn", results, is (not (empty ()))); assertThat ("Resultaten moeten directory-vermelding bevatten:", results, contains ("main", "test")); int exitCode = process.waitFor (); assertEquals ("Er mogen geen fouten worden gedetecteerd", 0, exitCode); }

In het bovenstaande voorbeeld stellen we de werkdirectory in op de src dir met behulp van de gemaksmethode directory (bestandsmap). Vervolgens voeren we een eenvoudige opdracht voor het weergeven van mappen uit en controleren of de uitvoer de submappen bevat hoofd en test.

3.4. Omleiden van standaardinvoer en -uitvoer

In de echte wereld zullen we waarschijnlijk de resultaten van onze lopende processen willen vastleggen in een logbestand voor verdere analyse. Gelukkig is het ProcessBuilder API heeft hiervoor ingebouwde ondersteuning, zoals we in dit voorbeeld zullen zien.

Standaard leest ons proces invoer van een pijp. We hebben toegang tot deze pijp via de outputstroom die wordt geretourneerd door Process.getOutputStream ().

Zoals we binnenkort zullen zien, kan de standaarduitvoer echter worden omgeleid naar een andere bron, zoals een bestand met behulp van de methode redirectOutput. In dit geval, getOutputStream () zal een ProcessBuilder.NullOutputStream.

Laten we terugkeren naar ons oorspronkelijke voorbeeld om de versie van Java af te drukken. Maar laten we deze keer de uitvoer omleiden naar een logbestand in plaats van de standaard uitvoerpijp:

ProcessBuilder processBuilder = nieuwe ProcessBuilder ("java", "-versie"); processBuilder.redirectErrorStream (true); Bestandslog = folder.newFile ("java-version.log"); processBuilder.redirectOutput (logboek); Proces process = processBuilder.start ();

In het bovenstaande voorbeeld we maken een nieuw tijdelijk bestand met de naam log en vertellen ons ProcessBuilder om de uitvoer naar deze bestandsbestemming om te leiden.

In dit laatste fragment controleren we dat gewoon getInputStream () is inderdaad nul en dat de inhoud van ons bestand is zoals verwacht:

assertEquals ("Indien omgeleid, zou -1 moeten zijn", -1, process.getInputStream (). read ()); List lines = Files.lines (log.toPath ()). Collect (Collectors.toList ()); assertThat ("Resultaten moeten java-versie bevatten:", lines, hasItem (containsString ("java-versie")));

Laten we nu eens kijken naar een kleine variatie op dit voorbeeld. Bijvoorbeeld als we iets aan een logbestand willen toevoegen in plaats van elke keer een nieuw bestand aan te maken:

Bestandslog = tempFolder.newFile ("java-version-append.log"); processBuilder.redirectErrorStream (true); processBuilder.redirectOutput (Redirect.appendTo (log));

Het is ook belangrijk om de oproep naar te vermelden redirectErrorStream (true). In het geval van fouten, wordt de foutuitvoer samengevoegd met het normale procesuitvoerbestand.

We kunnen natuurlijk individuele bestanden specificeren voor de standaarduitvoer en de standaardfoutuitvoer:

Bestand outputLog = tempFolder.newFile ("standard-output.log"); Bestand errorLog = tempFolder.newFile ("error.log"); processBuilder.redirectOutput (Redirect.appendTo (outputLog)); processBuilder.redirectError (Redirect.appendTo (errorLog));

3.5. De I / O van het huidige proces overnemen

In dit voorlaatste voorbeeld zien we de erfelijkheid () methode in actie. We kunnen deze methode gebruiken als we de I / O van het subproces willen omleiden naar de standaard I / O van het huidige proces:

@Test openbare ongeldig gegevenProcessBuilder_whenInheritIO_thenSuccess () gooit IOException, InterruptedException {ProcessBuilder processBuilder = nieuwe ProcessBuilder ("/ bin / sh", "-c", "echo hallo"); processBuilder.inheritIO (); Proces process = processBuilder.start (); int exitCode = process.waitFor (); assertEquals ("Er mogen geen fouten worden gedetecteerd", 0, exitCode); }

Gebruik in het bovenstaande voorbeeld de erfelijkheid () methode zien we de uitvoer van een eenvoudig commando in de console in onze IDE.

In de volgende sectie gaan we kijken welke toevoegingen aan het ProcessBuilder API in Java 9.

4. Java 9-toevoegingen

Java 9 introduceerde het concept van pijpleidingen in het ProcessBuilder API:

openbare statische lijst startPipeline (lijstbouwers) 

De ... gebruiken startPipeline methode kunnen we een lijst doorgeven van ProcessBuilder voorwerpen. Deze statische methode start dan een Werkwijze voor elk ProcessBuilder. Zo ontstaat een pijplijn van processen die met elkaar zijn verbonden door hun standaarduitvoer en standaard invoerstromen.

Als we bijvoorbeeld zoiets als dit willen uitvoeren:

vind . -naam * .java -type f | wc -l

Wat we zouden doen, is een procesbouwer maken voor elk geïsoleerd commando en deze samenstellen in een pijplijn:

@Test openbare ongeldige gegevenProcessBuilder_whenStartingPipeline_thenSuccess () gooit IOException, InterruptedException {List builders = Arrays.asList (nieuwe ProcessBuilder ("find", "src", "-name", "* .java", "-type", "f") , nieuwe ProcessBuilder ("wc", "-l")); Lijstprocessen = ProcessBuilder.startPipeline (builders); Proces laatste = processen.get (processen.size () - 1); Lijstuitvoer = readOutput (last.getInputStream ()); assertThat ("Resultaten mogen niet leeg zijn", output, is (not (empty ()))); }

In dit voorbeeld zoeken we naar alle Java-bestanden in de src directory en piping de resultaten naar een ander proces om ze te tellen.

Bekijk ons ​​geweldige artikel over Java 9 Process API-verbeteringen voor meer informatie over andere verbeteringen die zijn aangebracht aan de Process API in Java 9.

5. Conclusie

Samenvattend hebben we in deze tutorial de java.lang.ProcessBuilder API in detail.

Allereerst hebben we uitgelegd wat er met de API kan worden gedaan en hebben we de belangrijkste methoden samengevat.

Vervolgens hebben we een aantal praktijkvoorbeelden bekeken. Ten slotte hebben we gekeken naar welke nieuwe toevoegingen aan de API in Java 9 zijn geïntroduceerd.

Zoals altijd is de volledige broncode van het artikel beschikbaar op GitHub.