Programovanie

Keď Runtime.exec () nebude

Ako súčasť jazyka Java je java.lang balík je implicitne importovaný do každého programu Java. Úskalia tohto balíka často vyplávajú na povrch a ovplyvňujú väčšinu programátorov. Tento mesiac budem diskutovať o pasciach, ktoré na nás číhajú Runtime.exec () metóda.

Úskalie 4: Keď Runtime.exec () nebude

Trieda java.lang.Runtime obsahuje statickú metódu s názvom getRuntime (), ktorá načíta aktuálne Java Runtime Environment. Iba tak je možné získať odkaz na Beh programu objekt. S týmto odkazom môžete spustiť externé programy vyvolaním súboru Beh programu triedy exec () metóda. Vývojári často nazývajú túto metódu spustením prehľadávača na zobrazenie stránky pomocníka v jazyku HTML.

Existujú štyri preťažené verzie exec () príkaz:

  • public Process exec (príkaz String);
  • public Process exec (String [] cmdArray);
  • public Process exec (príkaz String, String [] envp);
  • public Process exec (String [] cmdArray, String [] envp);

Pre každú z týchto metód sa príkaz - a pravdepodobne aj sada argumentov - odovzdá do volania funkcie špecifického pre operačný systém. To následne vytvorí proces špecifický pre operačný systém (bežiaci program) s odkazom na a Proces triedy sa vrátil na Java VM. The Proces trieda je abstraktná trieda, pretože konkrétna podtrieda Proces pre každý operačný systém existuje.

Do týchto metód môžete vložiť tri možné vstupné parametre:

  1. Jeden reťazec, ktorý predstavuje program, ktorý sa má vykonať, aj všetky argumenty tohto programu
  2. Pole reťazcov, ktoré oddeľujú program od jeho argumentov
  3. Pole premenných prostredia

Vo formulári odovzdajte premenné prostredia meno = hodnota. Ak používate verziu exec () s jedným reťazcom pre program aj pre jeho argumenty, všimnite si, že reťazec je analyzovaný pomocou bieleho priestoru ako oddeľovača cez StringTokenizer trieda.

Narazenie na IllegalThreadStateException

Prvá nástraha týkajúca sa Runtime.exec () je IllegalThreadStateException. Prevládajúcim prvým testom API je kódovanie jeho najzrejmejších metód. Napríklad na vykonanie procesu, ktorý je externý k Java VM, používame exec () metóda. Aby sme videli hodnotu, ktorú vráti externý proces, použijeme exitValue () metóda na Proces trieda. V našom prvom príklade sa pokúsime spustiť kompilátor Java (javac.exe):

Výpis 4.1 BadExecJavac.java

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

Priebeh BadExecJavac vyrába:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java BadExecJavac java.lang.IllegalThreadStateException: proces nebol ukončený na java.lang.Win32Process.exitValue (natívna metóda) na BadExecJavac.main (BadExecJavac.java:13) 

Ak sa externý proces ešte nedokončil, exitValue () metóda hodí IllegalThreadStateException; preto tento program zlyhal. Aj keď dokumentácia uvádza túto skutočnosť, prečo nemôže táto metóda čakať na platnú odpoveď?

Podrobnejší pohľad na metódy dostupné v dokumente Proces trieda odhaľuje a čakať na() metóda, ktorá to robí presne. V skutočnosti, čakať na() vráti aj výstupnú hodnotu, čo znamená, že by ste ich nepoužili exitValue () a čakať na() v spojení navzájom, ale radšej by si vybrali jedno alebo druhé. Jediný možný čas, ktorý by ste využili exitValue () namiesto čakať na() by bolo, keď nechcete, aby váš program blokoval čakanie na externý proces, ktorý sa nemusí nikdy dokončiť. Namiesto použitia čakať na() metóda, najradšej by som odovzdal boolovský parameter s názvom čakať na do exitValue () metóda na určenie, či má súčasné vlákno čakať alebo nie. Boolean by bol prospešnejší, pretože exitValue () je vhodnejší názov pre túto metódu a nie je potrebné, aby dve metódy vykonávali tú istú funkciu za rôznych podmienok. Takáto jednoduchá diskriminácia podmienok je doménou vstupného parametra.

Preto, aby ste sa tejto pasci vyhli, buď chytte IllegalThreadStateException alebo počkajte na dokončenie procesu.

Teraz poďme vyriešiť problém v zozname 4.1 a počkajte na dokončenie procesu. V zozname 4.2 sa program znova pokúsi vykonať javac.exe a potom čaká na dokončenie externého procesu:

Zoznam 4.2 BadExecJavac2.java

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

Bohužiaľ, beh BadExecJavac2 neprodukuje žiadny výstup. Program visí a nikdy sa nedokončí. Prečo javac proces nikdy nedokončený?

Prečo Runtime.exec () visí

Odpoveď na túto otázku poskytuje dokumentácia JDK Javadoc.

Pretože niektoré natívne platformy poskytujú iba obmedzenú veľkosť vyrovnávacej pamäte pre štandardné vstupné a výstupné toky, zlyhanie rýchleho zápisu vstupného toku alebo načítania výstupného toku podprocesu môže spôsobiť zablokovanie podprocesu a dokonca jeho zablokovanie.

Je to iba prípad programátorov, ktorí nečítajú dokumentáciu, ako to vyplýva z často citovanej rady: prečítajte si pekný manuál (RTFM)? Odpoveď je čiastočne áno. V takom prípade by vás prečítanie Javadocu dostalo do polovice cesty; vysvetľuje, že musíte spracovať streamy do externého procesu, ale nehovorí vám, ako na to.

Hrá sa tu ďalšia premenná, čo je zrejmé z veľkého počtu programátorských otázok a mylných predstáv o tomto API v diskusných skupinách: aj keď Runtime.exec () a procesné API sa zdajú mimoriadne jednoduché, táto jednoduchosť klame, pretože jednoduché alebo zrejmé použitie API je náchylné na chyby. Ponaučením pre návrhára API je vyhradiť si jednoduché API pre jednoduché operácie. Operácie náchylné na zložitosť a závislosti na konkrétnej platforme by mali presne odrážať oblasť. Je možné, že sa abstrakcia prenesie príliš ďaleko. The JConfig Knižnica poskytuje príklad úplnejšieho API na spracovanie operácií so súbormi a procesmi (ďalšie informácie nájdete v zdrojoch nižšie).

Teraz sledujme dokumentáciu JDK a spracujme výstup z javac procesu. Keď bežíte javac bez akýchkoľvek argumentov produkuje množinu príkazov na použitie, ktoré popisujú, ako spustiť program, a význam všetkých dostupných možností programu. Vedieť, že to bude smerovať do stderr stream, môžete ľahko napísať program na jeho vyčerpanie pred čakaním na ukončenie procesu. Výpisom 4.3 sa táto úloha dokončuje. Aj keď tento prístup bude fungovať, nie je to dobré všeobecné riešenie. Takto je pomenovaný program Výpisu 4.3 MediocreExecJavac; poskytuje iba priemerné riešenie. Lepšie riešenie by vyprázdnilo štandardný prúd chýb aj štandardný výstupný prúd. A najlepším riešením by bolo súčasné vyprázdnenie týchto prúdov (ukážem to neskôr).

Výpis 4.3 MediocreExecJavac.java

import java.util. *; import java.io. *; public class MediocreExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Spracovať proc = rt.exec ("javac"); InputStream stderr = proc.getErrorStream (); InputStreamReader isr = nový InputStreamReader (stderr); BufferedReader br = nový BufferedReader (isr); Riadok reťazca = null; System.out.println (""); while ((riadok = br.readLine ())! = null) System.out.println (riadok); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Proces exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}} 

Priebeh MediocreExecJavac generuje:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java MediocreExecJavac Použitie: javac kde obsahuje: -g generovať všetky informácie o ladení -g: none generovať žiadne informácie o ladení -g: {lines, vars, source} generovať iba niektoré informácie o ladení -O optimalizovať; môže brániť v ladení alebo zväčšovať súbory triedy -nowarn Generovať žiadne varovania -verbose Výstupné správy o tom, čo robí kompilátor -deprecation Miesta zdroja výstupu, kde sa používajú zastarané API -classpath Zadať, kde nájsť súbory užívateľskej triedy -sourcepath Zadať, kde nájsť vstupné zdrojové súbory -bootclasspath Prepísať umiestnenie súborov triedy bootstrap -extdirs Prepísať umiestnenie nainštalovaných rozšírení -d Zadať, kam umiestniť generované súbory triedy -kódovanie Zadať kódovanie znakov používané zdrojovými súbormi -cieľ Generovať súbory triedy pre konkrétnu verziu VM Výstupná hodnota procesu: 2 

Takže MediocreExecJavac pracuje a produkuje výstupnú hodnotu 2. Výstupná hodnota je zvyčajne 0 naznačuje úspech; akákoľvek nenulová hodnota označuje chybu. Význam týchto ukončovacích hodnôt závisí od konkrétneho operačného systému. Chyba Win32 s hodnotou 2 je chyba „súbor sa nenašiel“. To dáva zmysel, pretože javac očakáva, že program zostavíme pomocou súboru zdrojového kódu.

Teda obísť druhú nástrahu - večne visieť v nej Runtime.exec () - ak program, ktorý spustíte, produkuje výstup alebo očakáva vstup, nezabudnite spracovať vstupné a výstupné toky.

Za predpokladu, že príkaz je spustiteľný program

V operačnom systéme Windows veľa nových programátorov narazilo Runtime.exec () pri pokuse o použitie pre nespustiteľné príkazy ako r a kópia. Následne narazia Runtime.exec ()tretia nástraha. Zoznam 4.4 ukazuje, že:

Výpis 4.4 BadExecWinDir.java

import java.util. *; import java.io. *; public class BadExecWinDir {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Spracovať proc = rt.exec ("dir"); InputStream stdin = proc.getInputStream (); InputStreamReader isr = nový InputStreamReader (stdin); BufferedReader br = nový BufferedReader (isr); Riadok reťazca = null; System.out.println (""); while ((riadok = br.readLine ())! = null) System.out.println (riadok); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Proces exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}} 

Priebeh BadExecWinDir vyrába:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java BadExecWinDir java.io.IOException: CreateProcess: dir error = 2 at java.lang.Win32Process.create (natívna metóda) na java.lang.Win32Process. (Neznámy zdroj) na java.lang.Runtime.execInternal (natívna metóda) na java.lang.Runtime.exec (neznámy zdroj) na java.lang.Runtime.exec (neznámy zdroj) na java.lang.Runtime.exec (neznámy zdroj) na java .lang.Runtime.exec (neznámy zdroj) na BadExecWinDir.main (BadExecWinDir.java:12) 

Ako už bolo uvedené, chybová hodnota je 2 znamená „súbor nebol nájdený“, čo v tomto prípade znamená, že je spustiteľný súbor pomenovaný dir.exe nepodarilo sa nájsť. Je to preto, že príkaz adresára je súčasťou tlmočníka príkazov systému Windows a nie samostatným spustiteľným súborom. Ak chcete spustiť interpret príkazov Windows, spustite buď command.com alebo cmd.exe, v závislosti od operačného systému Windows, ktorý používate. Výpis 4.5 spustí kópiu tlmočníka príkazov Windows a potom vykoná príkaz dodaný používateľom (napr. r).

Zoznam 4.5 GoodWindowsExec.java

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