Ohjelmointi

Kun Runtime.exec () ei

Osana Java-kieltä java.lang paketti tuodaan implisiittisesti kaikkiin Java-ohjelmiin. Tämän paketin sudenkuopat vaikuttavat usein useimpiin ohjelmoijiin. Tässä kuussa keskustelen Runtime.exec () menetelmä.

Pitfall 4: Kun Runtime.exec () ei

Luokka java.lang.Runtime sisältää staattisen menetelmän nimeltä getRuntime (), joka noutaa nykyisen Java Runtime Environment -ympäristön. Se on ainoa tapa saada viittaus Ajonaika esine. Tällä viitteellä voit suorittaa ulkoisia ohjelmia kutsumalla Ajonaika luokan exec () menetelmä. Kehittäjät kutsuvat tätä menetelmää usein käynnistääkseen selaimen ohjesivun näyttämiseksi HTML-muodossa.

Ohjelmasta on neljä ylikuormitettua versiota exec () komento:

  • public Process exec (merkkijono-komento);
  • julkinen prosessi exec (merkkijono [] cmdArray);
  • public Process exec (String-komento, String [] envp);
  • julkinen prosessi exec (String [] cmdArray, String [] envp);

Jokaiselle näistä menetelmistä komento - ja mahdollisesti joukko argumentteja - välitetään käyttöjärjestelmäkohtaiselle funktiokutsulle. Tämä luo myöhemmin käyttöjärjestelmäkohtaisen prosessin (käynnissä oleva ohjelma) viittaamalla a Prosessi luokka palasi Java VM: ään. Prosessi luokka on abstrakti luokka, koska tietty alaluokka Prosessi jokaiselle käyttöjärjestelmälle.

Voit välittää kolme mahdollista syöttöparametriä näihin menetelmiin:

  1. Yksi merkkijono, joka edustaa sekä suoritettavaa ohjelmaa että ohjelman mahdollisia argumentteja
  2. Joukko merkkijonoja, jotka erottavat ohjelman argumenteistaan
  3. Joukko ympäristömuuttujia

Syötä ympäristömuuttujat muodossa nimi = arvo. Jos käytät version exec () jossa on yksi merkkijono sekä ohjelmaa että sen argumentteja varten, huomaa, että merkkijono jäsennetään käyttäen välilyöntiä erottimena StringTokenizer luokassa.

Kompastuminen IllegalThreadStateExceptioniin

Ensimmäinen aiheeseen liittyvä kuoppa Runtime.exec () on IllegalThreadStateException. API: n yleisin ensimmäinen testi on koodata sen ilmeisimmät menetelmät. Esimerkiksi Java VM: n ulkopuolisen prosessin suorittamiseksi käytämme exec () menetelmä. Jos haluat nähdä arvon, jonka ulkoinen prosessi palauttaa, käytämme exitValue () menetelmä Prosessi luokassa. Ensimmäisessä esimerkissämme yritämme suorittaa Java-kääntäjän (javac.exe):

Listaus 4.1 BadExecJavac.java

tuo java.util. *; tuo java.io. *; julkinen luokka BadExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Prosessi proc = rt.exec ("javac"); int exitVal = proc.exitValue (); System.out.println ("Prosessin exitValue:" + exitVal); } saalis (heitettävä t) {t.printStackTrace (); }}} 

Juoksu BadExecJavac tuottaa:

E: \ class \ com \ javaworld \ jpitfalls \ Article2> java BadExecJavac java.lang.IllegalThreadStateException: prosessi ei ole poistunut osoitteesta java.lang.Win32Process.exitValue (Native Method) osoitteessa BadExecJavac.main (BadExecJavac.java:13) 

Jos ulkoinen prosessi ei ole vielä valmis, exitValue () menetelmä heittää IllegalThreadStateException; siksi tämä ohjelma epäonnistui. Vaikka dokumentaatiossa todetaan tämä tosiasia, miksi tämä menetelmä ei voi odottaa, kunnes se voi antaa pätevän vastauksen?

Tarkempi kuvaus sovelluksessa käytettävissä olevista menetelmistä Prosessi luokka paljastaa a odottaa() menetelmä, joka tekee juuri sen. Itse asiassa, odottaa() palauttaa myös exit-arvon, mikä tarkoittaa, ettet käyttäisi exitValue () ja odottaa() yhdessä toistensa kanssa, vaan valitsevat pikemminkin yhden tai toisen. Ainoa käyttämäsi aika exitValue () sijasta odottaa() olisi silloin, kun et halua, että ohjelmasi estä odottaa ulkoista prosessia, joka ei ehkä koskaan pääty loppuun. Sen sijaan, että käyttäisit odottaa() -menetelmällä haluaisin mieluummin välittää loogisen parametrin nimeltä odottaa osaksi exitValue () menetelmä sen määrittämiseksi, pitäisikö nykyisen ketjun odottaa. Totuusarvo olisi hyödyllisempi, koska exitValue () on sopivampi nimi tälle menetelmälle, eikä kahden menetelmän tarvitse suorittaa samaa toimintoa eri olosuhteissa. Tällainen yksinkertainen ehtoerotus on tuloparametrin toimialue.

Siksi tämän ansan välttämiseksi joko kiinni IllegalThreadStateException tai odota prosessin päättymistä.

Korjaa nyt ongelma Listaus 4.1: ssä ja odota prosessin päättymistä. Luettelossa 4.2 ohjelma yrittää jälleen suorittaa javac.exe ja odottaa sitten ulkoisen prosessin päättymistä:

Listaus 4.2 BadExecJavac2.java

tuo java.util. *; tuo java.io. *; julkinen luokka BadExecJavac2 {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Prosessi proc = rt.exec ("javac"); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } saalis (heitettävä t) {t.printStackTrace (); }}} 

Valitettavasti juosta BadExecJavac2 ei tuota mitään. Ohjelma jumittuu eikä koskaan toteudu. Miksi javac prosessi ei ole koskaan valmis?

Miksi Runtime.exec () jumittuu

JDK: n Javadoc-dokumentaatio antaa vastauksen tähän kysymykseen:

Koska jotkut natiivialustat tarjoavat vain rajoitetun puskurikoon tavallisille sisään- ja ulostulovirroille, epäonnistuminen syöttövirran nopeassa kirjoittamisessa tai aliprosessin lähtövirran lukemisessa voi aiheuttaa aliprosessin eston ja jopa umpikujan.

Onko kyse vain ohjelmoijista, jotka eivät lue dokumentaatiota, kuten usein lainatuista neuvoista käy ilmi: lue hienokäsikirja (RTFM)? Vastaus on osittain kyllä. Tässä tapauksessa Javadocin lukeminen vie sinut puoliväliin; se selittää, että ulkoisen prosessin virrat on käsiteltävä, mutta se ei kerro miten.

Toinen muuttuja on tässä pelissä, mikä käy ilmi ohjelmointikysymyksistä ja tätä API: ta koskevista väärinkäsityksistä uutisryhmissä: tosin Runtime.exec () ja prosessin sovellusliittymät näyttävät erittäin yksinkertaisilta, että yksinkertaisuus on harhaanjohtavaa, koska API: n yksinkertainen tai ilmeinen käyttö on altis virheille. Tässä oppitunti API-suunnittelijalle on varata yksinkertaiset API: t yksinkertaisille toiminnoille. Monimutkaisuuteen ja alustakohtaisiin riippuvuuksiin liittyvien toimintojen tulisi heijastaa toimialue tarkasti. Abstraktio on mahdollista kuljettaa liian pitkälle. JConfig kirjasto tarjoaa esimerkin täydellisemmästä sovellusliittymästä tiedostojen ja prosessien käsittelyyn (lisätietoja on alla olevissa Resursseissa).

Seuraetaan nyt JDK-dokumentaatiota ja käsitellään javac prosessi. Kun juokset javac ilman argumentteja se tuottaa joukon käyttölausekkeita, jotka kuvaavat ohjelman ajamista ja kaikkien käytettävissä olevien ohjelmavaihtoehtojen merkitystä. Tietäen, että tämä on menossa stderr suoratoistona, voit helposti kirjoittaa ohjelman tyhjentämään virta, ennen kuin odotat prosessin poistumista. Listaus 4.3 suorittaa tämän tehtävän loppuun. Vaikka tämä lähestymistapa toimii, se ei ole hyvä yleinen ratkaisu. Siksi Listing 4.3: n ohjelma on nimetty KeskinkertainenExecJavac; se tarjoaa vain keskinkertaisen ratkaisun. Parempi ratkaisu tyhjentäisi sekä tavallisen virhevirran että tavallisen ulostulovirran. Ja paras ratkaisu tyhjentäisi nämä virrat samanaikaisesti (osoitan sen myöhemmin).

Listaus 4.3 KeskinkertainenExecJavac.java

tuo java.util. *; tuo java.io. *; julkinen luokka MediocreExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Prosessi proc = rt.exec ("javac"); InputStream stderr = proc.getErrorStream (); InputStreamReader isr = uusi InputStreamReader (stderr); Puskuroitu lukija br = uusi puskuroitu lukija (isr); Merkkijono = nolla; System.out.println (""); while ((rivi = br.readLine ())! = null) System.out.println (rivi); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Prosessin exitValue:" + exitVal); } saalis (heitettävä t) {t.printStackTrace (); }}} 

Juoksu KeskinkertainenExecJavac tuottaa:

E: \ class \ com \ javaworld \ jpitfalls \ article2> java MediocreExecJavac Käyttö: javac johon sisältyy: -g Luo kaikki virheenkorjaustiedot -g: none Luo ei virheenkorjaustietoja -g: {lines, vars, source} Luo vain joitain virheenkorjaustietoja -O optimoi; saattaa haitata virheenkorjausta tai suurentaa luokkatiedostoja -varoita Ei luo varoituksia -verbose Lähtösanomat kääntäjän toiminnasta -deprecation Lähdekoodin sijainnit, joissa vanhentuneita sovellusliittymiä käytetään -classpath Määritä mistä löydät käyttäjäluokkatiedostot -ourcepath Määritä mistä etsitään lähdekooditiedostot -bootclasspath Ohita bootstrap-luokan tiedostojen sijainti -exddirs Ohita asennettujen laajennusten sijainti -d Määritä, mihin luodut luokkatiedostot sijoitetaan -koodaus Määritä lähdetiedostojen käyttämä merkkikoodaus -target Luo luokkatiedostot tietylle virtuaalikoneen versiolle Prosessi exitValue: 2 

Niin, KeskinkertainenExecJavac toimii ja tuottaa poistumisarvon 2. Normaalisti poistumisarvo on 0 osoittaa menestystä; mikä tahansa nolla-arvo ilmaisee virheen. Näiden poistumisarvojen merkitys riippuu tietystä käyttöjärjestelmästä. Win32-virhe, jonka arvo on 2 on "tiedostoa ei löydy" -virhe. Se on järkevää, koska javac odottaa meidän seuraavan ohjelmaa käännettävän lähdekooditiedoston kanssa.

Siten toisen kuopan kiertäminen - roikkuu ikuisesti sisään Runtime.exec () - Jos käynnistämäsi ohjelma tuottaa tulosta tai odottaa tuloa, varmista, että käsittelet tulo- ja lähtövirrat.

Komennon olettaminen on suoritettava ohjelma

Windows-käyttöjärjestelmässä monet uudet ohjelmoijat törmäävät Runtime.exec () kun yrität käyttää sitä suorittamattomiin komentoihin, kuten ohj ja kopio. Myöhemmin he törmäävät Runtime.exec ()kolmas kuoppa. Listaus 4.4 osoittaa tarkalleen, että:

Listaus 4.4 BadExecWinDir.java

tuo java.util. *; tuo java.io. *; public class BadExecWinDir {public static void main (String args []) {kokeile {Runtime rt = Runtime.getRuntime (); Prosessi proc = rt.exec ("dir"); InputStream stdin = proc.getInputStream (); InputStreamReader isr = uusi InputStreamReader (stdin); Puskuroitu lukija br = uusi puskuroitu lukija (isr); Merkkijono = nolla; System.out.println (""); while ((rivi = br.readLine ())! = null) System.out.println (rivi); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } saalis (heitettävä t) {t.printStackTrace (); }}} 

Juoksu BadExecWinDir tuottaa:

E: \ class \ com \ javaworld \ jpitfalls \ article2> java BadExecWinDir java.io.IOException: CreateProcess: dir virhe = 2 at java.lang.Win32Process.create (alkuperäinen menetelmä) osoitteessa java.lang.Win32Process. (Tuntematon lähde) at java.lang.Runtime.execInternal (Native Method) at java.lang.Runtime.exec (Tuntematon lähde) at java.lang.Runtime.exec (Tuntematon lähde) at java.lang.Runtime.exec (Tuntematon lähde) java .lang.Runtime.exec (tuntematon lähde) osoitteessa BadExecWinDir.main (BadExecWinDir.java:12) 

Kuten aiemmin todettiin, virhearvo 2 tarkoittaa "tiedostoa ei löydy", mikä tässä tapauksessa tarkoittaa, että suoritettava tiedosto on nimetty dir.exe ei voitu löytää. Tämä johtuu siitä, että hakemistokomento on osa Windowsin komentotulkkia eikä erillinen suoritettava tiedosto. Suorita Windows-komentotulkki suorittamalla joko command.com tai cmd.exe, riippuen käyttämästäsi Windows-käyttöjärjestelmästä. Listaus 4.5 suorittaa kopion Windowsin komentotulkista ja suorittaa sitten käyttäjän toimittaman komennon (esim. ohj).

Listaus 4.5 GoodWindowsExec.java

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