Apabila Runtime.exec () tidak akan

Sebagai bagian dari bahasa Java, java.langpaket ini secara implisit diimport ke setiap program Java. Perangkap pakej ini sering muncul, yang mempengaruhi kebanyakan pengaturcara. Bulan ini, saya akan membincangkan perangkap yang bersembunyi dalam Runtime.exec()kaedah ini.

Pitfall 4: Bila Runtime.exec () tidak akan

Kelas ini java.lang.Runtimemempunyai kaedah statik yang disebut getRuntime(), yang mengambil Java Runtime Environment semasa. Itulah satu-satunya cara untuk mendapatkan rujukan ke Runtimeobjek tersebut. Dengan rujukan itu, anda boleh menjalankan program luaran dengan menggunakan kaedah Runtimekelas exec(). Pembangun sering memanggil kaedah ini untuk melancarkan penyemak imbas untuk memaparkan halaman bantuan dalam HTML.

Terdapat empat versi exec()arahan yang terlalu banyak :

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

Untuk setiap kaedah ini, perintah - dan mungkin satu set argumen - diteruskan ke panggilan fungsi khusus sistem operasi. Ini seterusnya mewujudkan proses khusus sistem operasi (program yang berjalan) dengan rujukan ke Processkelas yang dikembalikan ke Java VM. The Processkelas adalah kelas abstrak, kerana subkelas tertentu Processwujud untuk setiap sistem operasi.

Anda boleh memasukkan tiga parameter input yang mungkin ke dalam kaedah ini:

  1. Rentetan tunggal yang mewakili kedua-dua program yang akan dijalankan dan sebarang argumen untuk program tersebut
  2. Susunan rentetan yang memisahkan program dari argumennya
  3. Pelbagai pemboleh ubah persekitaran

Lulus pemboleh ubah persekitaran dalam bentuk name=value. Sekiranya anda menggunakan versi exec()dengan rentetan tunggal untuk program dan argumennya, perhatikan bahawa rentetan diuraikan menggunakan ruang putih sebagai pembatas melalui StringTokenizerkelas.

Tersandung dalam Pengecualian IllegalThreadStateException

Perangkap pertama yang berkaitan Runtime.exec()adalah IllegalThreadStateException. Ujian pertama yang lazim dari API adalah mengkod kaedahnya yang paling jelas. Sebagai contoh, untuk menjalankan proses yang berada di luar Java VM, kami menggunakan exec()metode tersebut. Untuk melihat nilai yang dikembalikan oleh proses luaran, kami menggunakan exitValue()kaedah di Processkelas. Dalam contoh pertama kami, kami akan berusaha melaksanakan penyusun Java ( javac.exe):

Penyenaraian 4.1 BadExecJavac.java

import java.util. *; import java.io. *; kelas awam BadExecJavac {public static void main (String args []) {cuba {Runtime rt = Runtime.getRuntime (); Proses proc = rt.exec ("javac"); int exitVal = proc.exitValue (); System.out.println ("Process exitValue:" + exitVal); } tangkapan (Throwable t) {t.printStackTrace (); }}}

Satu siri BadExecJavacmenghasilkan:

E: \ class \ com \ javaworld \ jpitfalls \ article2> java BadExecJavac java.lang.IllegalThreadStateException: proses belum keluar di java.lang.Win32Process.exitValue (Kaedah Asli) di BadExecJavac.main (BadExecJavac.java:13) 

Sekiranya proses luaran belum selesai, exitValue()kaedah akan membuang IllegalThreadStateException; sebab itulah program ini gagal. Walaupun dokumentasi menyatakan fakta ini, mengapa kaedah ini tidak dapat menunggu sehingga dapat memberikan jawapan yang sah?

Pandangan yang lebih teliti mengenai kaedah yang ada di Processkelas menunjukkan waitFor()kaedah yang tepat. Sebenarnya, waitFor()juga mengembalikan nilai keluar, yang bermaksud bahawa anda tidak akan menggunakan exitValue()dan waitFor()bersama-sama satu sama lain, tetapi lebih memilih satu atau yang lain. Masa yang hanya boleh dilakukan anda akan menggunakan exitValue()dan bukannya waitFor()akan apabila anda tidak mahu program anda untuk menyekat menunggu proses luaran yang boleh tidak pernah selesai. Daripada menggunakan waitFor()kaedah, saya lebih suka meneruskan parameter boolean yang dipanggil waitForke dalam exitValue()kaedah untuk menentukan sama ada benang semasa harus menunggu atau tidak. Boolean akan lebih bermanfaat keranaexitValue()adalah nama yang lebih sesuai untuk kaedah ini, dan tidak perlu bagi dua kaedah untuk menjalankan fungsi yang sama dalam keadaan yang berbeza. Diskriminasi keadaan sederhana seperti itu adalah domain parameter input.

Oleh itu, untuk mengelakkan perangkap ini, baik menangkap IllegalThreadStateExceptionatau menunggu prosesnya selesai.

Sekarang, mari kita selesaikan masalah dalam Penyenaraian 4.1 dan tunggu prosesnya selesai. Dalam Penyenaraian 4.2, program sekali lagi cuba dijalankan javac.exedan kemudian menunggu proses luaran selesai:

Penyenaraian 4.2 BadExecJavac2.java

import java.util. *; import java.io. *; kelas awam BadExecJavac2 {public static void main (String args []) {cuba {Runtime rt = Runtime.getRuntime (); Proses proc = rt.exec ("javac"); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } tangkapan (Throwable t) {t.printStackTrace (); }}}

Malangnya, larian BadExecJavac2menghasilkan tidak menghasilkan output. Program ini digantung dan tidak pernah selesai. Mengapa javacproses itu tidak pernah selesai?

Mengapa Runtime.exec () hang

Dokumentasi JDK's Javadoc memberikan jawapan kepada soalan ini:

Kerana sebilangan platform asli hanya menyediakan ukuran penyangga terhad untuk aliran input dan output standard, kegagalan untuk segera menulis aliran input atau membaca aliran output subproses boleh menyebabkan subproses menjadi tersekat, dan bahkan kebuntuan.

Adakah ini hanya masalah pengaturcara yang tidak membaca dokumentasi, seperti yang tersirat dalam nasihat yang sering disebut: baca manual yang baik (RTFM)? Jawapannya adalah sebahagiannya ya. Dalam kes ini, membaca Javadoc akan membawa anda separuh jalan; ini menjelaskan bahawa anda perlu menangani aliran ke proses luaran anda, tetapi ia tidak memberitahu anda bagaimana.

Pemboleh ubah lain dimainkan di sini, seperti yang ditunjukkan oleh sebilangan besar pertanyaan dan salah tanggapan pengaturcara mengenai API ini dalam kumpulan berita: walaupun Runtime.exec()dan API Proses kelihatan sangat sederhana, kesederhanaan itu menipu kerana penggunaan API yang mudah, atau jelas, terdedah kepada kesalahan. Pelajaran di sini untuk pereka API adalah menempah API sederhana untuk operasi mudah. Operasi yang terdedah kepada kerumitan dan pergantungan khusus platform harus mencerminkan domain dengan tepat. Pengabstrakan boleh dilakukan terlalu jauh. The JConfigperpustakaan menyediakan contoh API yang lebih lengkap untuk operasi fail pemegang dan proses (lihat Sumber di bawah untuk maklumat lanjut).

Sekarang, mari ikuti dokumentasi JDK dan menangani output javacprosesnya. Apabila anda berjalan javactanpa sebarang argumen, ia menghasilkan satu set pernyataan penggunaan yang menerangkan bagaimana menjalankan program dan makna semua pilihan program yang tersedia. Dengan mengetahui bahawa ini akan stderrmengalir, anda boleh menulis program untuk mengalirkan aliran dengan mudah sebelum menunggu proses keluar. Penyenaraian 4.3 menyelesaikan tugas itu. Walaupun pendekatan ini akan berjaya, itu bukan penyelesaian umum yang baik. Oleh itu, program Penyenaraian 4.3 dinamakan MediocreExecJavac; ia hanya memberikan penyelesaian biasa-biasa sahaja. Penyelesaian yang lebih baik akan mengosongkan aliran ralat standard dan aliran output standard. Dan penyelesaian terbaik akan mengosongkan aliran ini secara serentak (saya akan menunjukkannya kemudian).

Penyenaraian 4.3 MediocreExecJavac.java

import java.util. *; import java.io. *; kelas awam MediocreExecJavac {public static void main (String args []) {cuba {Runtime rt = Runtime.getRuntime (); Proses proc = rt.exec ("javac"); InputStream stderr = proc.getErrorStream (); InputStreamReader isr = InputStreamReader baru (stderr); BufferedReader br = BufferedReader baru (isr); Garisan tali = null; System.out.println (""); sementara ((line = br.readLine ())! = null) System.out.println (baris); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } tangkapan (Throwable t) {t.printStackTrace (); }}}

Rangkaian MediocreExecJavacmenghasilkan:

E: \ class \ com \ javaworld \ jpitfalls \ article2> java MediocreExecJavac Penggunaan: javac di mana merangkumi: -g Menjana semua maklumat penyahpepijatan -g: tiada Menghasilkan maklumat penyahpepijatan -g: {lines, vars, source} Hasilkan hanya beberapa maklumat debug -O Optimumkan; mungkin menghalang penyahpepijatan atau memperbesar fail kelas -sekarang Menjana tidak ada amaran -mesej Output mengenai apa yang dilakukan penyusun -deprecation Lokasi sumber output di mana API yang digunakan tidak digunakan -classpath Menentukan di mana untuk mencari fail kelas pengguna -sourcepath Menentukan tempat untuk mencari fail sumber input -booting

So, MediocreExecJavac works and produces an exit value of 2. Normally, an exit value of 0 indicates success; any nonzero value indicates an error. The meaning of these exit values depends on the particular operating system. A Win32 error with a value of 2 is a "file not found" error. That makes sense, since javac expects us to follow the program with the source code file to compile.

Thus, to circumvent the second pitfall -- hanging forever in Runtime.exec() -- if the program you launch produces output or expects input, ensure that you process the input and output streams.

Assuming a command is an executable 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) 

Seperti yang dinyatakan sebelumnya, nilai kesalahan 2bermaksud "fail tidak dijumpai," yang, dalam hal ini, bermaksud bahawa nama yang dir.exedapat dilaksanakan tidak dapat dijumpai. Ini kerana arahan direktori adalah sebahagian daripada pentafsir perintah Windows dan bukan pelaksanaan yang terpisah. Untuk menjalankan jurubahasa perintah Windows, jalankan salah satu command.comatau cmd.exe, bergantung pada sistem operasi Windows yang anda gunakan. Penyenaraian 4.5 menjalankan salinan jurubahasa perintah Windows dan kemudian melaksanakan perintah yang dibekalkan pengguna (misalnya, dir).

Penyenaraian 4.5 GoodWindowsExec.java