Programmering

Når Runtime.exec () ikke vil

Som en del av Java-språket, er java.lang pakken er implisitt importert til hvert Java-program. Denne pakkens fallgruver dukker ofte opp, og berører de fleste programmerere. Denne måneden skal jeg diskutere fellene som lurer i Runtime.exec () metode.

Pitfall 4: Når Runtime.exec () ikke vil

Klassen java.lang.Runtime har en statisk metode kalt getRuntime (), som henter gjeldende Java Runtime Environment. Det er den eneste måten å få en referanse til Kjøretid gjenstand. Med den referansen kan du kjøre eksterne programmer ved å påkalle Kjøretid klassen utføre () metode. Utviklere kaller ofte denne metoden for å starte en nettleser for å vise en hjelpeside i HTML.

Det er fire overbelastede versjoner av utføre () kommando:

  • offentlig prosessutførelse (strengkommando);
  • public Process exec (String [] cmdArray);
  • public Process exec (String command, String [] envp);
  • public Process exec (String [] cmdArray, String [] envp);

For hver av disse metodene overføres en kommando - og muligens et sett med argumenter - til et operativsystem-spesifikk funksjonsanrop. Dette oppretter deretter en operativsystemspesifikk prosess (et kjørende program) med en referanse til a Prosess klasse tilbake til Java VM. De Prosess klasse er en abstrakt klasse, fordi en spesifikk underklasse av Prosess eksisterer for hvert operativsystem.

Du kan sende tre mulige inngangsparametere til disse metodene:

  1. En enkelt streng som representerer både programmet som skal utføres, og eventuelle argumenter for det programmet
  2. En rekke strenger som skiller programmet fra argumentene
  3. En rekke miljøvariabler

Gi miljøvariablene i skjemaet navn = verdi. Hvis du bruker versjonen av utføre () med en enkelt streng for både programmet og dets argumenter, vær oppmerksom på at strengen blir analysert ved å bruke hvitt mellomrom som skilletegn via StringTokenizer klasse.

Snubler inn i et ulovligThreadStateException

Den første fallgruven knyttet til Runtime.exec () er den IllegalThreadStateException. Den vanligste første testen av et API er å kode de mest åpenbare metodene. For eksempel, for å utføre en prosess som er ekstern til Java VM, bruker vi utføre () metode. For å se verdien som den eksterne prosessen returnerer, bruker vi exitValue () metoden på Prosess klasse. I vårt første eksempel vil vi prøve å utføre Java-kompilatoren (javac.exe):

Oppføring 4.1 BadExecJavac.java

importer java.util. *; importer java.io. *; public class BadExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Prosess proc = rt.exec ("javac"); int exitVal = proc.exitValue (); System.out.println ("Prosess exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}} 

Et løp av BadExecJavac produserer:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java BadExecJavac java.lang.IllegalThreadStateException: prosessen er ikke avsluttet på java.lang.Win32Process.exitValue (Native Method) på BadExecJavac.main (BadExecJavac.java:13) 

Hvis en ekstern prosess ennå ikke er fullført, vil exitValue () metoden vil kaste en IllegalThreadStateException; det er derfor dette programmet mislyktes. Mens dokumentasjonen sier dette, hvorfor kan ikke denne metoden vente til den kan gi et gyldig svar?

En grundigere titt på metodene som er tilgjengelige i Prosess klasse avslører en vent for() metode som gjør nettopp det. Faktisk, vent for() returnerer også utgangsverdien, noe som betyr at du ikke vil bruke exitValue () og vent for() i forbindelse med hverandre, men heller velge det ene eller det andre. Den eneste mulige tiden du vil bruke exitValue () i stedet for vent for() ville være når du ikke vil at programmet skal blokkere venting på en ekstern prosess som kanskje aldri fullføres. I stedet for å bruke vent for() metode, foretrekker jeg å sende en boolsk parameter kalt vent for inn i det exitValue () metode for å bestemme om den gjeldende tråden skal vente eller ikke. En boolsk ville være mer fordelaktig fordi exitValue () er et mer passende navn for denne metoden, og det er ikke nødvendig for to metoder å utføre den samme funksjonen under forskjellige forhold. En slik enkel tilstandsdiskriminering er domenet til en inputparameter.

Derfor, for å unngå denne fellen, må du enten fange IllegalThreadStateException eller vent til prosessen er fullført.

La oss nå fikse problemet i Listing 4.1 og vente på at prosessen er fullført. I oppføring 4.2 prøver programmet igjen å utføre javac.exe og venter på at den eksterne prosessen skal fullføres:

Oppføring 4.2 BadExecJavac2.java

importer java.util. *; importer java.io. *; public class BadExecJavac2 {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Prosess proc = rt.exec ("javac"); int exitVal = proc.waitFor (); System.out.println ("Prosess exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}} 

Dessverre et løp av BadExecJavac2 gir ingen ytelse. Programmet henger og fullføres aldri. Hvorfor gjør javac prosessen aldri fullført?

Hvorfor Runtime.exec () henger

JDKs Javadoc-dokumentasjon gir svaret på dette spørsmålet:

Fordi noen innfødte plattformer bare gir begrenset bufferstørrelse for standard inngangs- og utgangsstrømmer, kan unnlatelse av å umiddelbart skrive inngangsstrømmen eller lese utstrømmen til underprosessen føre til at underprosessen blokkeres, og til og med låsing.

Er dette bare et tilfelle av at programmerere ikke leser dokumentasjonen, som antydet i det ofte siterte rådet: Les finmanualen (RTFM)? Svaret er delvis ja. I dette tilfellet vil det å lese Javadoc komme deg halvveis dit; det forklarer at du trenger å håndtere strømmer til den eksterne prosessen, men det forteller deg ikke hvordan.

En annen variabel er på spill her, som det fremgår av det store antallet programmeringsspørsmål og misoppfatninger angående dette API i nyhetsgruppene: skjønt Runtime.exec () og prosess-API-ene virker ekstremt enkle, at enkelheten bedrar fordi den enkle eller åpenbare bruken av API-en er utsatt for feil. Leksjonen her for API-designeren er å reservere enkle APIer for enkle operasjoner. Operasjoner utsatt for kompleksitet og plattformsspesifikke avhengigheter bør gjenspeile domenet nøyaktig. Det er mulig at en abstraksjon blir ført for langt. De JConfig biblioteket gir et eksempel på en mer komplett API for å håndtere fil- og prosessoperasjoner (se Ressurser nedenfor for mer informasjon).

La oss nå følge JDK-dokumentasjonen og håndtere utdataene fra javac prosess. Når du løper javac uten argumenter produserer den et sett med brukserklæringer som beskriver hvordan du kjører programmet og betydningen av alle tilgjengelige programalternativer. Å vite at dette kommer til stderr stream, kan du enkelt skrive et program for å tømme den strømmen før du venter på at prosessen skal avsluttes. Oppføring 4.3 fullfører oppgaven. Selv om denne tilnærmingen vil fungere, er det ikke en god generell løsning. Dermed får Listing 4.3 sitt program navn MiddelmådigExecJavac; det gir bare en middelmådig løsning. En bedre løsning ville tømme både standard feilstrøm og standard utgangsstrøm. Og den beste løsningen ville tømme disse strømningene samtidig (jeg vil demonstrere det senere).

Oppføring 4.3 MediocreExecJavac.java

importer java.util. *; importer java.io. *; public class MediocreExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Prosess proc = rt.exec ("javac"); InputStream stderr = proc.getErrorStream (); InputStreamReader isr = ny InputStreamReader (stderr); BufferedReader br = ny BufferedReader (isr); Strenglinje = null; System.out.println (""); mens ((linje = br.readLine ())! = null) System.out.println (linje); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Prosess exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}} 

Et løp av MiddelmådigExecJavac genererer:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java MediocreExecJavac Bruk: javac hvor inkluderer: -g Generer all feilsøkingsinformasjon -g: ingen Generer ingen feilsøkingsinformasjon -g: {linjer, vars, kilde} Generer bare noe feilsøkingsinformasjon -O Optimaliser; kan hindre feilsøking eller forstørre klassefiler -nowarn Generer ingen advarsler -verbose Output-meldinger om hva kompilatoren gjør -deprecation Utdatakildeplasser hvor utdaterte API-er brukes -classpath Spesifiser hvor du skal finne brukerklassfiler -sourcepath Spesifiser hvor du finner inngangskildefiler -bootclasspath Overstyr plassering av bootstrap-klassefiler -extdirs Overstyr plassering av installerte utvidelser -d Angi hvor genererte klassefiler skal plasseres -koding Spesifiser tegnkoding som brukes av kildefiler -target Generer klassefiler for spesifikk VM-versjon Prosess exitValue: 2 

Så, MiddelmådigExecJavac fungerer og produserer en utgangsverdi på 2. Normalt en utgangsverdi på 0 indikerer suksess; enhver verdi som ikke er null indikerer en feil. Betydningen av disse utgangsverdiene avhenger av det aktuelle operativsystemet. En Win32-feil med verdien 2 er en "fil ikke funnet" feil. Det er fornuftig siden javac forventer at vi følger programmet med kildekodefilen vi skal kompilere.

Dermed å omgå den andre fallgruven - hengende for alltid i Runtime.exec () - hvis programmet du starter produserer output eller forventer input, må du sørge for at du behandler input og output streams.

Forutsatt at en kommando er et kjørbart program

Under Windows-operativsystemet snubler mange nye programmerere over Runtime.exec () når du prøver å bruke den til ikke-kjørbare kommandoer som dir og kopiere. Deretter kjører de inn Runtime.exec ()tredje fallgruve. Oppføring 4.4 viser nøyaktig det:

Oppføring 4.4 BadExecWinDir.java

importer java.util. *; importer java.io. *; public class BadExecWinDir {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Prosess proc = rt.exec ("dir"); InputStream stdin = proc.getInputStream (); InputStreamReader isr = ny InputStreamReader (stdin); BufferedReader br = ny BufferedReader (isr); Strenglinje = null; System.out.println (""); mens ((linje = br.readLine ())! = null) System.out.println (linje); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Prosess exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}} 

Et løp av BadExecWinDir produserer:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java BadExecWinDir java.io.IOException: CreateProcess: dir error = 2 at java.lang.Win32Process.create (Native Method) at java.lang.Win32Process. (Ukjent kilde) at java.lang.Runtime.execInternal (Native Method) at java.lang.Runtime.exec (Unknown Source) at java.lang.Runtime.exec (Unknown Source) at java.lang.Runtime.exec (Unknown Source) at java .lang.Runtime.exec (ukjent kilde) på BadExecWinDir.main (BadExecWinDir.java:12) 

Som nevnt tidligere er feilverdien på 2 betyr "fil ikke funnet", som i dette tilfellet betyr at den kjørbare filen heter dir.exe kunne ikke bli funnet. Det er fordi katalogkommandoen er en del av Windows kommandotolk og ikke en separat kjørbar. For å kjøre Windows kommandotolker, kjør enten command.com eller cmd.exe, avhengig av Windows-operativsystemet du bruker. Oppføring 4.5 kjører en kopi av Windows-kommandotolken og utfører deretter den brukerleverte kommandoen (f.eks. dir).

Oppføring 4.5 GoodWindowsExec.java

$config[zx-auto] not found$config[zx-overlay] not found