Kada Runtime.exec () neće

Kao dio Java jezika, java.langpaket se implicitno uvozi u svaki Java program. Zamke ovog paketa često isplivaju na površinu, što pogađa većinu programera. Ovog mjeseca razgovarat ću o zamkama koje vrebaju Runtime.exec()metodu.

Zamka 4: Kada Runtime.exec () neće

Klasa java.lang.Runtimesadrži statičku metodu getRuntime()koja poziva trenutni Java Runtime Environment. To je jedini način da se dobije referenca na Runtimeobjekt. S tom referencom možete pokrenuti vanjske programe pozivajući se Runtimena exec()metodu klase . Programeri ovu metodu često pozivaju kako bi pokrenuli preglednik za prikaz stranice pomoći u HTML-u.

Postoje četiri preopterećene verzije exec()naredbe:

  • public Process exec(String command);
  • public Process exec(String [] cmdArray);
  • public Process exec(String command, String [] envp);
  • public Process exec(String [] cmdArray, String [] envp);

Za svaku od ovih metoda naredba - i možda skup argumenata - prosljeđuje se pozivu funkcije specifičnom za operativni sustav. To naknadno stvara proces specifičan za operativni sustav (pokrenut program) s referencom na Processklasu vraćenu u Java VM. ProcessKlasa je apstraktna klasa, jer je specifična podvrsta Processpostoji za svaki operativni sustav.

U ove metode možete proslijediti tri moguća ulazna parametra:

  1. Pojedinačni niz koji predstavlja program koji treba izvršiti i sve argumente tom programu
  2. Niz nizova koji odvajaju program od njegovih argumenata
  3. Niz varijabli okoline

Unesite varijable okoline u obrazac name=value. Ako koristite verziju exec()s jednim nizom i za program i za njegove argumente, imajte na umu da se niz raščlanjuje pomoću razmaka kao graničnika kroz StringTokenizerklasu.

Naletjevši na IllegalThreadStateException

Prva zamka koja se Runtime.exec()odnosi na IllegalThreadStateException. Prevladavajući prvi test API-ja je kodiranje njegovih najočitijih metoda. Na primjer, za izvršenje postupka koji je vanjski za Java VM, koristimo exec()metodu. Da bismo vidjeli vrijednost koju vanjski proces vraća, koristimo exitValue()metodu na Processklasi. U našem prvom primjeru pokušat ćemo izvršiti Java kompajler ( javac.exe):

Popis 4.1 BadExecJavac.java

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

Niz BadExecJavacproizvedenih:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java BadExecJavac java.lang.IllegalThreadStateException: proces nije izašao na java.lang.Win32Process.exitValue (izvorna metoda) na BadExecJavac.main (BadExejac. 

Ako vanjski postupak još nije završen, exitValue()metoda će baciti znak IllegalThreadStateException; zato je ovaj program propao. Iako dokumentacija navodi ovu činjenicu, zašto ova metoda ne može pričekati dok ne da valjani odgovor?

Temeljitiji pregled metoda dostupnih u Processrazredu otkriva waitFor()metodu koja upravo to čini. U stvari, waitFor()također vraća vrijednost izlaza, što znači da ne biste koristili exitValue()niti waitFor()u međusobnoj povezanosti, već biste odabrali jedno ili drugo. Jedino moguće vrijeme koje biste exitValue()umjesto toga koristili waitFor()bilo je kada ne želite da vaš program blokira čekanje na vanjskom procesu koji se možda nikada neće dovršiti. Umjesto da koristim waitFor()metodu, radije bih proslijedio logički parametar pozvan waitForu exitValue()metodu kako bih utvrdio treba li trenutna nit pričekati ili ne. Booleova vrijednost bila bi korisnija jerexitValue()je prikladniji naziv za ovu metodu i nije potrebno da dvije metode izvode istu funkciju pod različitim uvjetima. Takva jednostavna diskriminacija uvjeta domena je ulaznog parametra.

Stoga, da biste izbjegli ovu zamku, ili uhvatite IllegalThreadStateExceptionili pričekajte da postupak završi.

Idemo sada riješiti problem iz popisa 4.1 i pričekati da se postupak dovrši. U Popisu 4.2, program ponovno pokušava izvršiti, javac.exea zatim čeka da vanjski postupak završi:

Popis 4.2 BadExecJavac2.java

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

Nažalost, serija BadExecJavac2proizvedenih rezultata ne daje izlaz. Program visi i nikada se ne dovršava. Zašto se javacpostupak nikad ne dovršava?

Zašto visi Runtime.exec ()

JDK-ova Javadoc dokumentacija daje odgovor na ovo pitanje:

Budući da neke izvorne platforme pružaju ograničenu veličinu međuspremnika samo za standardne ulazne i izlazne tokove, neuspjeh brzog upisivanja ulaznog toka ili čitanja izlaznog toka potprocesa može uzrokovati blokadu potprocesa, pa čak i zastoj.

Je li ovo samo slučaj da programeri ne čitaju dokumentaciju, kao što se podrazumijeva u često citiranom savjetu: pročitajte fini priručnik (RTFM)? Odgovor je djelomično da. U ovom slučaju, čitanje Javadoca dovelo bi vas do pola puta; objašnjava da trebate rukovati strujama prema vašem vanjskom procesu, ali vam ne govori kako.

Ovdje je u igri još jedna varijabla, što je vidljivo iz velikog broja programerovih pitanja i zabluda u vezi s ovim API-jem u vijestima: iako se Runtime.exec()i Process API-ji čine krajnje jednostavnima, ta jednostavnost obmanjuje jer je jednostavna ili očita uporaba API-ja je sklon pogreškama. Ovdje je lekcija za API dizajnera rezerviranje jednostavnih API-ja za jednostavne operacije. Operacije sklone složenosti i ovisnostima specifičnim za platformu trebale bi točno odražavati domenu. Moguće je da se apstrakcija nosi predaleko. JConfigKnjižnica pruža primjer potpuniji API za obradu datoteka i procesa poslovanja (vidi dolje Resursi za više informacija).

Sada, slijedimo JDK dokumentaciju i rukujmo rezultatima javacpostupka. Kada izvodite javacbez ikakvih argumenata, on stvara skup izjava o upotrebi koji opisuju kako pokrenuti program i značenje svih dostupnih programskih opcija. Znajući da ovo ide u stderrtok, lako možete napisati program koji će taj tok iscrpiti prije nego što pričekate da postupak izađe. Popis 4.3 dovršava taj zadatak. Iako će ovaj pristup funkcionirati, to nije dobro opće rješenje. Dakle, imenovan je program izlista 4.3 MediocreExecJavac; pruža samo osrednje rješenje. Bolje rješenje ispraznilo bi i standardni tok pogrešaka i standardni izlazni tok. A najbolje rješenje bi istodobno ispraznilo te tokove (to ću pokazati kasnije).

Popis 4.3 MediocreExecJavac.java

uvoz java.util. *; import java.io. *; javna klasa MediocreExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Proces proc = rt.exec ("javac"); InputStream stderr = proc.getErrorStream (); InputStreamReader isr = novi InputStreamReader (stderr); BufferedReader br = novi BufferedReader (isr); Linija niza = null; System.out.println (""); while ((linija = br.readLine ())! = null) System.out.println (linija); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Process outputValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}}

Niz MediocreExecJavacgenerira:

E: \ classes \ com \ javaworld \ jpitfalls \ article2> java MediocreExecJavac Upotreba: javac gdje uključuje: -g Generiraj sve informacije o otklanjanju pogrešaka -g: nema Generiraj informacije o otklanjanju pogrešaka -g: {lines, vars, source} Generiraj samo neke informacije o otklanjanju pogrešaka -O Optimizirati; može ometati ispravljanje pogrešaka ili povećavati datoteke klasa -spoznati Ne generirati upozorenja -velike izlazne poruke o tome što prevodilac radi -odustani Izlazne lokacije izvora na kojima se koriste zastarjeli API-ji -classpath Navedite gdje pronaći datoteke korisničke klase -sourcepath Navedite gdje pronaći ulazne izvorne datoteke -bootclasspath Nadjačavanje mjesta datoteka klase bootstrap -extdirs Nadjačavanje mjesta instaliranih ekstenzija -d Navedite gdje smjestiti generirane datoteke klase -encoding Navedite kodiranje znakova koje koriste izvorne datoteke -cilj Generiranje datoteka klase za određenu verziju VM-a Process outputValue: 2

Dakle, MediocreExecJavacradi i proizvodi izlaznu vrijednost od 2. Obično vrijednost izlaza 0označava uspjeh; bilo koja različita od nule vrijednost ukazuje na pogrešku. Značenje ovih izlaznih vrijednosti ovisi o određenom operacijskom sustavu. Pogreška Win32 s vrijednošću 2je pogreška "datoteka nije pronađena". To ima smisla, jer od javacnas očekuje da slijedimo program s datotekom izvornog koda za kompajliranje.

Stoga, da biste zaobišli drugu zamku - koja zauvijek visi Runtime.exec()- ako program koji pokrenete daje izlaz ili očekuje ulaz, osigurajte obradu ulaznih i izlaznih tokova.

Pod pretpostavkom da je naredba izvršni program

Under the Windows operating system, many new programmers stumble upon Runtime.exec() when trying to use it for nonexecutable commands like dir and copy. Subsequently, they run into Runtime.exec()'s third pitfall. Listing 4.4 demonstrates exactly that:

Listing 4.4 BadExecWinDir.java

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

A run of BadExecWinDir produces:

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.(Unknown Source) 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(Unknown Source) at BadExecWinDir.main(BadExecWinDir.java:12) 

Kao što je ranije rečeno, vrijednost pogreške 2znači "datoteka nije pronađena", što u ovom slučaju znači da izvršna datoteka s imenom dir.exenije mogla biti pronađena. To je zato što je naredba direktorija dio interpretatora naredbi za Windows, a ne zasebna izvršna datoteka. Da biste pokrenuli tumač naredbi Windows, pokrenite bilo command.comili cmd.exe, ovisno o operativnom sustavu Windows koji koristite. Na popisu 4.5 pokreće se kopija Windows interpretatora naredbi, a zatim izvršava naredba koju je dostavio korisnik (npr. dir).

Popis 4.5 GoodWindowsExec.java