Java >> Java tutorial >  >> Tag >> java.lang

Guide til java.lang.ProcessBuilder API

1. Oversigt

Process API giver en effektiv måde at udføre operativsystemkommandoer i Java. Den har dog flere muligheder, der kan gøre den besværlig at arbejde med.

I dette selvstudie skal vi se på, hvordan Java afhjælper det med ProcessBuilder API.

2. ProcessBuilder API

ProcessBuilder klasse giver metoder til at oprette og konfigurere operativsystemprocesser. Hver ProcessBuilder instans giver os mulighed for at administrere en samling af procesattributter . Vi kan derefter starte en ny proces med de givne attributter.

Her er et par almindelige scenarier, hvor vi kunne bruge denne API:

  • Find den aktuelle Java-version
  • Opsæt et tilpasset nøgleværdikort til vores miljø
  • Skift arbejdsbiblioteket for, hvor vores shell-kommando kører
  • Omdiriger input- og outputstrømme til tilpassede erstatninger
  • Arv begge streams af den aktuelle JVM -proces
  • Udfør en shell-kommando fra Java-kode

Vi tager et kig på praktiske eksempler for hver af disse i senere afsnit.

Men før vi dykker ned i arbejdskoden, lad os tage et kig på, hvilken slags funktionalitet denne API tilbyder.

2.1. Metodeoversigt

I dette afsnit går vi et skridt tilbage og ser kort på de vigtigste metoder i ProcessBuilder klasse . Dette vil hjælpe os, når vi senere dykker ned i nogle rigtige eksempler:

  • ProcessBuilder(String... command)

    For at oprette en ny procesbygger med det angivne operativsystemprogram og argumenter kan vi bruge denne praktiske konstruktør.

  • directory(File directory)

    Vi kan tilsidesætte standardarbejdsmappen for den aktuelle proces ved at kalde mappen metode og sende en fil objekt. Som standard er den aktuelle arbejdsmappe indstillet til den værdi, der returneres af user.dir systemegenskab .

  • environment()

    Hvis vi ønsker at få de aktuelle miljøvariabler, kan vi blot kalde miljøet metode. Det returnerer os en kopi af det aktuelle procesmiljø ved hjælp af System.getenv() men som et kort .

  • inheritIO()

    Hvis vi ønsker at specificere, at kilden og destinationen for vores underprocesstandard I/O skal være den samme som for den aktuelle Java-proces, kan vi bruge inheritIO metode.

  • redirectInput(File file), redirectOutput(File file), redirectError(File file)

    Når vi ønsker at omdirigere procesbyggerens standard input, output og fejldestination til en fil, har vi disse tre lignende omdirigeringsmetoder til vores rådighed.

  • start()

    Sidst, men ikke mindst, for at starte en ny proces med det, vi har konfigureret, kalder vi simpelthen start() .

Vi skal bemærke, at denne klasse IKKE er synkroniseret . For eksempel, hvis vi har flere tråde, der får adgang til en ProcessBuilder forekomst samtidig, så skal synkroniseringen administreres eksternt.

3. Eksempler

Nu hvor vi har en grundlæggende forståelse af ProcessBuilder API, lad os gennemgå nogle eksempler.

3.1. Brug af ProcessBuilder at udskrive versionen af ​​Java

I dette første eksempel kører vi java kommando med ét argument for at få versionen .

Process process = new ProcessBuilder("java", "-version").start();

Først opretter vi vores ProcessBuilder objekt, der sender kommando- og argumentværdierne til konstruktøren. Dernæst starter vi processen ved hjælp af start() metode til at få en proces objekt.

Lad os nu se, hvordan man håndterer output:

List<String> results = readOutput(process.getInputStream());

assertThat("Results should not be empty", results, is(not(empty())));
assertThat("Results should contain java version: ", results, hasItem(containsString("java version")));

int exitCode = process.waitFor();
assertEquals("No errors should be detected", 0, exitCode);

Her læser vi procesoutputtet og kontrollerer, at indholdet er, som vi forventer. I det sidste trin venter vi på, at processen er færdig med at bruge process.waitFor() .

Når processen er afsluttet, fortæller returværdien os, om processen var vellykket eller ej .

Et par vigtige punkter at huske på:

  • Argumenterne skal være i den rigtige rækkefølge
  • I dette eksempel bruges standardarbejdsbiblioteket og -miljøet desuden
  • Vi kalder bevidst ikke process.waitFor() indtil efter vi har læst outputtet, fordi outputbufferen muligvis stopper processen
  • Vi har gjort den antagelse, at java kommandoen er tilgængelig via PATH variabel

3.2. Start af en proces med et ændret miljø

I dette næste eksempel skal vi se, hvordan man ændrer arbejdsmiljøet.

Men før vi gør det, lad os begynde med at tage et kig på den slags information, vi kan finde i standardmiljøet :

ProcessBuilder processBuilder = new ProcessBuilder();        
Map<String, String> environment = processBuilder.environment();
environment.forEach((key, value) -> System.out.println(key + value));

Dette udskriver ganske enkelt hver af de variabelindtastninger, der leveres som standard:

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

Nu skal vi tilføje en ny miljøvariabel til vores ProcessBuilder objekt og kør en kommando for at udlæse dets værdi:

environment.put("GREETING", "Hola Mundo");

processBuilder.command("/bin/bash", "-c", "echo $GREETING");
Process process = processBuilder.start();

Lad os opdele trinene for at forstå, hvad vi har gjort:

  • Tilføj en variabel kaldet 'GREETING' med værdien 'Hola Mundo' til vores miljø, som er et standard Map
  • Denne gang, i stedet for at bruge konstruktøren, indstiller vi kommandoen og argumenterne via kommando(String…-kommandoen) metode direkte.
  • Vi starter derefter vores proces som i det foregående eksempel.

For at fuldende eksemplet bekræfter vi, at outputtet indeholder vores hilsen:

List<String> results = readOutput(process.getInputStream());
assertThat("Results should not be empty", results, is(not(empty())));
assertThat("Results should contain java version: ", results, hasItem(containsString("Hola Mundo")));

3.3. Start af en proces med en ændret arbejdsmappe

Nogle gange kan det være nyttigt at ændre arbejdsbiblioteket . I vores næste eksempel skal vi se, hvordan man gør netop det:

@Test
public void givenProcessBuilder_whenModifyWorkingDir_thenSuccess() 
  throws IOException, InterruptedException {
    ProcessBuilder processBuilder = new ProcessBuilder("/bin/sh", "-c", "ls");

    processBuilder.directory(new File("src"));
    Process process = processBuilder.start();

    List<String> results = readOutput(process.getInputStream());
    assertThat("Results should not be empty", results, is(not(empty())));
    assertThat("Results should contain directory listing: ", results, contains("main", "test"));

    int exitCode = process.waitFor();
    assertEquals("No errors should be detected", 0, exitCode);
}

I ovenstående eksempel indstiller vi arbejdsmappen til projektets src dir ved hjælp af bekvemmelighedsmetoden bibliotek(Filmappe) . Vi kører derefter en simpel mappelistekommando og kontrollerer, at outputtet indeholder undermapperne main og test .

3.4. Omdirigering af standardinput og -output

I den virkelige verden vil vi sandsynligvis fange resultaterne af vores kørende processer i en logfil til yderligere analyse . Heldigvis ProcessBuilder API har indbygget understøttelse for netop dette, som vi vil se i dette eksempel.

Som standard læser vores proces input fra et rør. Vi kan få adgang til dette rør via outputstrømmen returneret af Process.getOutputStream() .

Men som vi snart vil se, kan standardoutputtet blive omdirigeret til en anden kilde, såsom en fil ved hjælp af metoden redirectOutput . I dette tilfælde getOutputStream() returnerer en ProcessBuilder.NullOutputStream .

Lad os vende tilbage til vores originale eksempel for at udskrive versionen af ​​Java. Men lad os denne gang omdirigere outputtet til en logfil i stedet for standardoutputrøret:

ProcessBuilder processBuilder = new ProcessBuilder("java", "-version");

processBuilder.redirectErrorStream(true);
File log = folder.newFile("java-version.log");
processBuilder.redirectOutput(log);

Process process = processBuilder.start();

I ovenstående eksempel opretter vi en ny midlertidig fil kaldet log og fortæller vores ProcessBuilder for at omdirigere output til denne fildestination .

I dette sidste uddrag tjekker vi blot at getInputStream() er faktisk nul og at indholdet af vores fil er som forventet:

assertEquals("If redirected, should be -1 ", -1, process.getInputStream().read());
List<String> lines = Files.lines(log.toPath()).collect(Collectors.toList());
assertThat("Results should contain java version: ", lines, hasItem(containsString("java version")));

Lad os nu tage et kig på en lille variation af dette eksempel. For eksempel når vi ønsker at tilføje til en logfil i stedet for at oprette en ny hver gang :

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

Det er også vigtigt at nævne opkaldet til redirectErrorStream(true). I tilfælde af fejl, vil fejloutputtet blive flettet ind i den normale procesoutputfil.

Vi kan selvfølgelig specificere individuelle filer for standardoutput og standardfejloutput:

File outputLog = tempFolder.newFile("standard-output.log");
File errorLog = tempFolder.newFile("error.log");

processBuilder.redirectOutput(Redirect.appendTo(outputLog));
processBuilder.redirectError(Redirect.appendTo(errorLog));

3.5. Arver I/O fra den aktuelle proces

I dette næstsidste eksempel vil vi se inheritIO() metode i aktion. Vi kan bruge denne metode, når vi vil omdirigere underproces I/O til standard I/O for den aktuelle proces:

@Test
public void givenProcessBuilder_whenInheritIO_thenSuccess() throws IOException, InterruptedException {
    ProcessBuilder processBuilder = new ProcessBuilder("/bin/sh", "-c", "echo hello");

    processBuilder.inheritIO();
    Process process = processBuilder.start();

    int exitCode = process.waitFor();
    assertEquals("No errors should be detected", 0, exitCode);
}

I ovenstående eksempel, ved at bruge inheritIO() metode, ser vi output fra en simpel kommando i konsollen i vores IDE.

I det næste afsnit skal vi se på, hvilke tilføjelser der blev foretaget til ProcessBuilder API i Java 9.

4. Java 9 tilføjelser

Java 9 introducerede konceptet pipelines til ProcessBuilder API:

public static List<Process> startPipeline​(List<ProcessBuilder> builders)

Brug af startPipeline metode kan vi sende en liste over ProcessBuilder genstande. Denne statiske metode vil derefter starte en proces for hver ProcessBuilder . Således skabes en pipeline af processer, som er forbundet med deres standard output og standard inputstrømme.

For eksempel, hvis vi vil køre noget som dette:

find . -name *.java -type f | wc -l

Det, vi ville gøre, er at oprette en procesbygger for hver isoleret kommando og sammensætte dem til en pipeline:

@Test
public void givenProcessBuilder_whenStartingPipeline_thenSuccess()
  throws IOException, InterruptedException {
    List builders = Arrays.asList(
      new ProcessBuilder("find", "src", "-name", "*.java", "-type", "f"), 
      new ProcessBuilder("wc", "-l"));

    List processes = ProcessBuilder.startPipeline(builders);
    Process last = processes.get(processes.size() - 1);

    List output = readOutput(last.getInputStream());
    assertThat("Results should not be empty", output, is(not(empty())));
}

I dette eksempel søger vi efter alle java-filerne inde i src bibliotek og overføre resultaterne til en anden proces for at tælle dem.

For at lære om andre forbedringer af Process API i Java 9, se vores fantastiske artikel om Java 9 Process API Improvements.

5. Konklusion

For at opsummere har vi i dette selvstudium udforsket java.lang.ProcessBuilder API i detaljer.

Først startede vi med at forklare, hvad der kan gøres med API'et og opsummerede de vigtigste metoder.

Dernæst tog vi et kig på en række praktiske eksempler. Til sidst så vi på, hvilke nye tilføjelser der blev introduceret til API'et i Java 9.

Som altid er den fulde kildekode til artiklen tilgængelig på GitHub.


Java tag