Programování

Když Runtime.exec () nebude

Jako součást jazyka Java je java.lang balíček je implicitně importován do každého programu Java. Úskalí tohoto balíčku se často objevují a ovlivňují většinu programátorů. Tento měsíc proberu pasti číhající na Runtime.exec () metoda.

Úskalí 4: Když Runtime.exec () nebude

Třída java.lang.Runtime obsahuje statickou metodu nazvanou getRuntime (), který načte aktuální prostředí Java Runtime Environment. To je jediný způsob, jak získat odkaz na Runtime objekt. S touto referencí můžete spustit externí programy vyvoláním Runtime třídy exec () metoda. Tuto metodu vývojáři často nazývají spuštěním prohlížeče pro zobrazení stránky nápovědy v HTML.

Existují čtyři přetížené verze exec () příkaz:

  • public Process exec (příkaz String);
  • public Process exec (String [] cmdArray);
  • public Process exec (příkaz String, řetězec [] envp);
  • public Process exec (String [] cmdArray, String [] envp);

Pro každou z těchto metod je příkaz - a případně sada argumentů - předán volání funkce specifické pro operační systém. To následně vytvoří proces specifický pro operační systém (běžící program) s odkazem na a Proces třída se vrátila na Java VM. The Proces třída je abstraktní třída, protože konkrétní podtřída Proces pro každý operační systém existuje.

Do těchto metod můžete předat tři možné vstupní parametry:

  1. Jeden řetězec, který představuje jak program k provedení, tak všechny argumenty k tomuto programu
  2. Pole řetězců, které oddělují program od jeho argumentů
  3. Pole proměnných prostředí

Předejte proměnné prostředí ve formuláři jméno = hodnota. Pokud používáte verzi exec () s jediným řetězcem jak pro program, tak pro jeho argumenty, všimněte si, že řetězec je analyzován pomocí mezer jako oddělovače pomocí StringTokenizer třída.

Klopýtnutí do IllegalThreadStateException

První úskalí týkající se Runtime.exec () je IllegalThreadStateException. Převládajícím prvním testem API je kódování jeho nejzřejmějších metod. Například k provedení procesu, který je externí k virtuálnímu počítači Java, používáme exec () metoda. Abychom viděli hodnotu, kterou externí proces vrací, použijeme exitValue () metoda na Proces třída. V našem prvním příkladu se pokusíme spustit kompilátor Java (javac.exe):

Výpis 4.1 BadExecJavac.java

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

Spuštění BadExecJavac vyrábí:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java BadExecJavac java.lang.IllegalThreadStateException: proces nebyl ukončen na java.lang.Win32Process.exitValue (nativní metoda) na BadExecJavac.main (BadExecJavac.java:13) 

Pokud externí proces ještě nebyl dokončen, exitValue () metoda hodí IllegalThreadStateException; proto tento program selhal. Zatímco dokumentace uvádí tuto skutečnost, proč tato metoda nemůže čekat, dokud nebude moci poskytnout platnou odpověď?

Důkladnější pohled na metody dostupné v Proces třída odhaluje a čekat na() metoda, která přesně to dělá. Ve skutečnosti, čekat na() také vrátí výstupní hodnotu, což znamená, že byste ji nepoužili exitValue () a čekat na() ve vzájemném spojení, ale raději by si vybrali jedno nebo druhé. Jediný možný čas, který byste využili exitValue () namísto čekat na() by bylo, když nechcete, aby váš program blokoval čekání na externí proces, který se nemusí nikdy dokončit. Místo použití čekat na() metoda, raději bych předal logický parametr s názvem čekat na do exitValue () metoda k určení, zda má aktuální vlákno čekat. Boolean by byl výhodnější, protože exitValue () je vhodnější název pro tuto metodu a není nutné, aby dvě metody prováděly stejnou funkci za různých podmínek. Taková jednoduchá diskriminace podmínek je doménou vstupního parametru.

Abyste se této pasti vyhnuli, chyťte buď IllegalThreadStateException nebo počkejte na dokončení procesu.

Nyní pojďme opravit problém v seznamu 4.1 a počkejte, až se proces dokončí. V seznamu 4.2 se program znovu pokusí provést javac.exe a pak čeká na dokončení externího procesu:

Výpis 4.2 BadExecJavac2.java

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

Bohužel běh BadExecJavac2 neprodukuje žádný výstup. Program zablokuje a nikdy nedokončí. Proč javac proces nikdy nedokončený?

Proč Runtime.exec () visí

Odpověď na tuto otázku poskytuje dokumentace Javadoc JDK:

Protože některé nativní platformy poskytují pouze omezenou velikost vyrovnávací paměti pro standardní vstupní a výstupní toky, selhání okamžitého zápisu vstupního proudu nebo čtení výstupního proudu podprocesu může způsobit zablokování podprocesu a dokonce zablokování.

Je to jen případ programátorů, kteří nečtou dokumentaci, jak vyplývá z často citované rady: přečtěte si jemný manuál (RTFM)? Odpověď je částečně ano. V tomto případě by vás čtení Javadocu dostalo do poloviny; vysvětluje, že musíte zpracovat streamy do externího procesu, ale neříká vám, jak na to.

Zde hraje další proměnná, jak je patrné z velkého počtu programátorských otázek a mylných představ o tomto API v diskusních skupinách: ačkoli Runtime.exec () a procesní API se zdají extrémně jednoduché, tato jednoduchost klame, protože jednoduché nebo zřejmé použití API je náchylné k chybám. Lekce zde pro návrháře API je rezervovat jednoduchá API pro jednoduché operace. Operace náchylné ke složitostem a závislostem na konkrétní platformě by měly přesně odrážet doménu. Je možné, že abstrakce bude provedena příliš daleko. The JConfig Knihovna poskytuje příklad úplnějšího rozhraní API pro zpracování operací se soubory a procesy (další informace viz Zdroje níže).

Nyní pojďme sledovat dokumentaci JDK a zpracovat výstup javac proces. Když běžíš javac bez jakýchkoli argumentů vytváří sadu příkazů k použití, které popisují, jak spustit program a význam všech dostupných možností programu. S vědomím, že to jde do stderr stream, můžete snadno napsat program, který tento stream vyčerpá, než čeká na ukončení procesu. Výpis 4.3 tento úkol dokončí. I když tento přístup bude fungovat, není to dobré obecné řešení. Tak je pojmenován program Výpisu 4.3 PrůměrnýJavac; poskytuje pouze průměrné řešení. Lepší řešení by vyprázdnilo standardní proud chyb i standardní výstupní proud. A nejlepším řešením by bylo tyto proudy vyprázdnit současně (to ukážu později).

Výpis 4.3 MediocreExecJavac.java

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

Spuštění PrůměrnýJavac generuje:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java MediocreExecJavac Použití: javac kde zahrnuje: -g generovat všechny informace o ladění -g: none generovat žádné informace o ladění -g: {lines, vars, source} generovat pouze některé informace o ladění -O optimalizovat; může bránit ladění nebo zvětšit soubory třídy -nowarn Generovat žádná varování -verbose Výstupní zprávy o tom, co kompilátor dělá -deprecation Umístění výstupních zdrojů, kde se používají zastaralé API -classpath Určete, kde najít soubory uživatelské třídy -sourcepath Určete, kde hledat vstupní zdrojové soubory -bootclasspath Přepsat umístění souborů třídy bootstrap -extdirs Přepsat umístění nainstalovaných přípon -d Určete, kam umístit vygenerované soubory třídy -encoding Určete kódování znaků používané zdrojovými soubory -target Generovat soubory třídy pro konkrétní verzi virtuálního počítače Proces exitValue: 2 

Tak, PrůměrnýJavac pracuje a vytváří výstupní hodnotu ve výši 2. Normálně je výstupní hodnota 0 označuje úspěch; jakákoli nenulová hodnota označuje chybu. Význam těchto hodnot ukončení závisí na konkrétním operačním systému. Chyba Win32 s hodnotou 2 je chyba „soubor nebyl nalezen“. To dává smysl, protože javac očekává, že budeme při kompilaci sledovat program se souborem zdrojového kódu.

Obejít tak druhou nástrahu - věčně viset Runtime.exec () - pokud spuštěný program produkuje výstup nebo očekává vstup, zajistěte zpracování vstupních a výstupních toků.

Za předpokladu, že příkaz je spustitelný program

V operačním systému Windows narazí mnoho nových programátorů Runtime.exec () při pokusu o použití pro neproveditelné příkazy jako dir a kopírovat. Následně narazí Runtime.exec ()třetí úskalí. Výpis 4.4 přesně ukazuje, že:

Výpis 4.4 BadExecWinDir.java

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

Spuštění BadExecWinDir vyrábí:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java BadExecWinDir java.io.IOException: CreateProcess: dir error = 2 at java.lang.Win32Process.create (nativní metoda) na java.lang.Win32Process. (Neznámý zdroj) 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 (neznámý zdroj) na BadExecWinDir.main (BadExecWinDir.java:12) 

Jak již bylo uvedeno výše, chybová hodnota 2 znamená „soubor nebyl nalezen“, což v tomto případě znamená spustitelný soubor s názvem dir.exe nemůže být nalezeno. Je to proto, že příkaz adresáře je součástí interpretu příkazů systému Windows a není samostatným spustitelným programem. Chcete-li spustit překladač příkazů Windows, proveďte buď command.com nebo cmd.exe, v závislosti na operačním systému Windows, který používáte. Výpis 4.5 spustí kopii interpretu příkazů systému Windows a poté provede příkaz dodaný uživatelem (např. dir).

Výpis 4.5 GoodWindowsExec.java

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