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:
- Jeden řetězec, který představuje jak program k provedení, tak všechny argumenty k tomuto programu
- Pole řetězců, které oddělují program od jeho argumentů
- 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