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:
- Yksi merkkijono, joka edustaa sekä suoritettavaa ohjelmaa että ohjelman mahdollisia argumentteja
- Joukko merkkijonoja, jotka erottavat ohjelman argumenteistaan
- 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