Προγραμματισμός

Όταν το Runtime.exec () δεν θα

Ως μέρος της γλώσσας Java, το java.lang Το πακέτο εισάγεται σιωπηρά σε κάθε πρόγραμμα Java. Οι παγίδες αυτού του πακέτου εμφανίζονται συχνά, επηρεάζοντας τους περισσότερους προγραμματιστές. Αυτό το μήνα, θα συζητήσω τις παγίδες που κρύβονται στο Runtime.exec () μέθοδος.

Pitfall 4: Όταν δεν θα το Runtime.exec ()

Η τάξη java.lang.Τρόπος εκτέλεσης διαθέτει μια στατική μέθοδο που ονομάζεται getRuntime (), που ανακτά το τρέχον Java Runtime Environment. Αυτός είναι ο μόνος τρόπος για να λάβετε μια αναφορά στο Χρόνος εκτέλεσης αντικείμενο. Με αυτήν την αναφορά, μπορείτε να εκτελέσετε εξωτερικά προγράμματα επικαλούμενος το Χρόνος εκτέλεσης της τάξης exec () μέθοδος. Οι προγραμματιστές συχνά καλούν αυτή τη μέθοδο για να εκκινήσουν ένα πρόγραμμα περιήγησης για την εμφάνιση μιας σελίδας βοήθειας σε HTML.

Υπάρχουν τέσσερις υπερφορτωμένες εκδόσεις του exec () εντολή:

  • δημόσια διαδικασία exec (εντολή String);
  • δημόσια διαδικασία exec (String [] cmdArray);
  • δημόσια διαδικασία exec (εντολή String, String [] envp);
  • δημόσια διαδικασία exec (String [] cmdArray, String [] envp);

Για καθεμία από αυτές τις μεθόδους, μια εντολή - και πιθανώς ένα σύνολο ορισμάτων - μεταβιβάζεται σε μια κλήση συνάρτησης για λειτουργικό σύστημα. Αυτό στη συνέχεια δημιουργεί μια διαδικασία συγκεκριμένη για λειτουργικό σύστημα (ένα τρέχον πρόγραμμα) με αναφορά σε ένα Επεξεργάζομαι, διαδικασία τάξη επέστρεψε στο Java VM. ο Επεξεργάζομαι, διαδικασία class είναι μια αφηρημένη τάξη, επειδή μια συγκεκριμένη υποκατηγορία του Επεξεργάζομαι, διαδικασία υπάρχει για κάθε λειτουργικό σύστημα.

Μπορείτε να περάσετε τρεις πιθανές παραμέτρους εισόδου σε αυτές τις μεθόδους:

  1. Μια μεμονωμένη συμβολοσειρά που αντιπροσωπεύει τόσο το πρόγραμμα που θα εκτελεστεί όσο και τυχόν επιχειρήματα για αυτό το πρόγραμμα
  2. Ένας πίνακας συμβολοσειρών που διαχωρίζουν το πρόγραμμα από τα ορίσματά του
  3. Ένας πίνακας μεταβλητών περιβάλλοντος

Περάστε τις μεταβλητές περιβάλλοντος στη φόρμα όνομα = τιμή. Εάν χρησιμοποιείτε την έκδοση του exec () με μία συμβολοσειρά τόσο για το πρόγραμμα όσο και για τα ορίσματά του, σημειώστε ότι η συμβολοσειρά αναλύεται χρησιμοποιώντας λευκό κενό ως οριοθέτη StringTokenizer τάξη.

Σκοπεύοντας σε ένα IllegalThreadStateException

Η πρώτη παγίδα που σχετίζεται με Runtime.exec () είναι το IllegalThreadStateException. Η επικρατούσα πρώτη δοκιμή ενός API είναι ο κωδικός των πιο προφανών μεθόδων του. Για παράδειγμα, για να εκτελέσετε μια διαδικασία που είναι εξωτερική του Java VM, χρησιμοποιούμε το exec () μέθοδος. Για να δούμε την τιμή που επιστρέφει η εξωτερική διαδικασία, χρησιμοποιούμε το exitValue () μέθοδος στο Επεξεργάζομαι, διαδικασία τάξη. Στο πρώτο μας παράδειγμα, θα προσπαθήσουμε να εκτελέσουμε τον μεταγλωττιστή Java (javac.exe):

Λίστα 4.1 BadExecJavac.java

εισαγωγή java.util. *; εισαγωγή java.io. *; δημόσια τάξη BadExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Διαδικασία proc = rt.exec ("javac"); int exitVal = proc.exitValue (); System.out.println ("Process exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}} 

Μια σειρά από BadExecJavac παράγει:

E: \ class \ com \ javaworld \ jpitfalls \ article2> java BadExecJavac java.lang.IllegalThreadStateException: η διαδικασία δεν έχει κλείσει στο java.lang.Win32Process.exitValue (εγγενής μέθοδος) στο BadExecJavac.main (BadExecJavac.java:13) 

Εάν μια εξωτερική διαδικασία δεν έχει ακόμη ολοκληρωθεί, το exitValue () μέθοδος θα ρίξει ένα IllegalThreadStateException; γι 'αυτό το πρόγραμμα απέτυχε. Ενώ η τεκμηρίωση δηλώνει αυτό το γεγονός, γιατί αυτή η μέθοδος δεν μπορεί να περιμένει μέχρι να δώσει μια έγκυρη απάντηση;

Μια πιο διεξοδική ματιά στις διαθέσιμες μεθόδους στο Επεξεργάζομαι, διαδικασία τάξη αποκαλύπτει ένα περίμενε για() μέθοδος που κάνει ακριβώς αυτό. Στην πραγματικότητα, περίμενε για() επιστρέφει επίσης την τιμή εξόδου, που σημαίνει ότι δεν θα χρησιμοποιούσατε exitValue () και περίμενε για() σε συνδυασμό μεταξύ τους, αλλά μάλλον θα επιλέγουν το ένα ή το άλλο. Η μόνη δυνατή στιγμή που θα χρησιμοποιούσατε exitValue () αντί περίμενε για() θα ήταν όταν δεν θέλετε το πρόγραμμά σας να αποκλείει την αναμονή σε μια εξωτερική διαδικασία που μπορεί να μην ολοκληρωθεί ποτέ. Αντί να χρησιμοποιήσετε το περίμενε για() μέθοδος, θα προτιμούσα να περάσω μια δυαδική παράμετρο που ονομάζεται περίμενε για μέσα στο exitValue () μέθοδος για να προσδιορίσετε αν το τρέχον νήμα πρέπει να περιμένει. Ένα boolean θα ήταν πιο ευεργετικό γιατί exitValue () είναι ένα πιο κατάλληλο όνομα για αυτήν τη μέθοδο και δεν είναι απαραίτητο για δύο μεθόδους να εκτελούν την ίδια λειτουργία υπό διαφορετικές συνθήκες. Αυτή η απλή διάκριση κατάστασης είναι ο τομέας μιας παραμέτρου εισαγωγής.

Επομένως, για να αποφύγετε αυτήν την παγίδα, πιάστε το IllegalThreadStateException ή περιμένετε να ολοκληρωθεί η διαδικασία.

Τώρα, ας διορθώσουμε το πρόβλημα στην Λίστα 4.1 και περιμένουμε να ολοκληρωθεί η διαδικασία. Στην Λίστα 4.2, το πρόγραμμα προσπαθεί ξανά να εκτελέσει javac.exe και στη συνέχεια περιμένει την ολοκλήρωση της εξωτερικής διαδικασίας:

Λίστα 4.2 BadExecJavac2.java

εισαγωγή java.util. *; εισαγωγή java.io. *; δημόσια τάξη BadExecJavac2 {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Διαδικασία proc = rt.exec ("javac"); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}} 

Δυστυχώς, μια σειρά BadExecJavac2 δεν παράγει έξοδο. Το πρόγραμμα κρέμεται και δεν ολοκληρώνεται ποτέ. Γιατί το κάνει javac η διαδικασία δεν ολοκληρώθηκε ποτέ;

Γιατί κρέμεται το Runtime.exec ()

Η τεκμηρίωση του JDK Javadoc παρέχει την απάντηση σε αυτήν την ερώτηση:

Επειδή ορισμένες εγγενείς πλατφόρμες παρέχουν μόνο περιορισμένο μέγεθος buffer για τυπικές ροές εισόδου και εξόδου, η αποτυχία εγγραφής αμέσως της ροής εισόδου ή ανάγνωσης της ροής εξόδου της υποεπεξεργασίας μπορεί να προκαλέσει τον αποκλεισμό της υποεπεξεργασίας και ακόμη και το αδιέξοδο.

Μήπως αυτή η περίπτωση προγραμματιστών δεν διαβάζει την τεκμηρίωση, όπως υπονοείται από τις συχνά αναφερόμενες συμβουλές: διαβάστε το καλό εγχειρίδιο (RTFM); Η απάντηση είναι εν μέρει ναι. Σε αυτήν την περίπτωση, η ανάγνωση του Javadoc θα σας έφερνε στα μισά του δρόμου. εξηγεί ότι πρέπει να χειριστείτε τις ροές στην εξωτερική σας διαδικασία, αλλά δεν σας λέει πώς.

Μια άλλη μεταβλητή βρίσκεται εδώ, όπως φαίνεται από τον μεγάλο αριθμό ερωτήσεων προγραμματιστών και παρανοήσεων σχετικά με αυτό το API στις ομάδες συζήτησης: ωστόσο Runtime.exec () και τα API επεξεργασίας φαίνονται εξαιρετικά απλά, ότι η απλότητα είναι παραπλανητική επειδή η απλή, ή προφανής, χρήση του API είναι επιρρεπής σε σφάλματα. Το μάθημα εδώ για τον σχεδιαστή API είναι να κρατήσει απλά API για απλές λειτουργίες. Οι λειτουργίες που είναι επιρρεπείς σε πολυπλοκότητες και εξαρτήσεις ειδικά για πλατφόρμα πρέπει να αντικατοπτρίζουν τον τομέα με ακρίβεια. Είναι δυνατόν μια αφαίρεση να μεταφέρεται πολύ μακριά. ο JConfig Η βιβλιοθήκη παρέχει ένα παράδειγμα πληρέστερου API για τον χειρισμό αρχείων και διαδικασιών επεξεργασίας (ανατρέξτε στην ενότητα Πόροι παρακάτω για περισσότερες πληροφορίες).

Τώρα, ας ακολουθήσουμε την τεκμηρίωση JDK και χειριστούμε την έξοδο του javac επεξεργάζομαι, διαδικασία. Όταν τρέχετε javac χωρίς ορίσματα, παράγει ένα σύνολο δηλώσεων χρήσης που περιγράφουν τον τρόπο εκτέλεσης του προγράμματος και τη σημασία όλων των διαθέσιμων επιλογών προγράμματος. Γνωρίζοντας ότι πρόκειται για το stderr ροή, μπορείτε εύκολα να γράψετε ένα πρόγραμμα για να εξαντλήσετε αυτήν τη ροή πριν περιμένετε να τερματίσει η διαδικασία. Η λίστα 4.3 ολοκληρώνει αυτήν την εργασία. Ενώ αυτή η προσέγγιση θα λειτουργήσει, δεν είναι μια καλή γενική λύση. Έτσι, ονομάζεται το πρόγραμμα List 4.3 MediocreExecJavac; Παρέχει μόνο μια μέτρια λύση. Μια καλύτερη λύση θα αδειάσει τόσο την τυπική ροή σφαλμάτων όσο και την τυπική ροή εξόδου. Και η καλύτερη λύση θα αδειάσει αυτές τις ροές ταυτόχρονα (θα το δείξω αργότερα).

Καταχώριση 4.3 MediocreExecJavac.java

εισαγωγή java.util. *; εισαγωγή java.io. *; δημόσια τάξη MediocreExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Διαδικασία proc = rt.exec ("javac"); InputStream stderr = proc.getErrorStream (); InputStreamReader isr = νέο InputStreamReader (stderr); BufferedReader br = νέο BufferedReader (isr); Γραμμή συμβολοσειράς = null; System.out.println (""); ενώ ((γραμμή = br.readLine ())! = null) System.out.println (γραμμή); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}} 

Μια σειρά από MediocreExecJavac δημιουργεί:

E: \ class \ com \ javaworld \ jpitfalls \ article2> java MediocreExecJavac Χρήση: javac όπου περιλαμβάνει: -g Δημιουργία όλων των πληροφοριών εντοπισμού σφαλμάτων -g: κανένα Δημιουργία πληροφοριών αποσφαλμάτωσης -g: {lines, vars, source} Δημιουργία μόνο ορισμένων πληροφοριών εντοπισμού σφαλμάτων -O Βελτιστοποίηση μπορεί να εμποδίσει τον εντοπισμό σφαλμάτων ή να μεγεθύνει αρχεία κλάσης - τώρα Δεν δημιουργεί προειδοποιήσεις - υπερβολικά μηνύματα εξόδου σχετικά με το τι κάνει ο μεταγλωττιστής - αποσυμπίεση Θέσεις προέλευσης εξόδου όπου χρησιμοποιούνται καταργημένα API -classpath Καθορίστε πού θα βρείτε αρχεία κατηγορίας χρήστη -sourcepath Καθορίστε πού θα βρείτε αρχεία πηγής εισόδου -bootclasspath Παράκαμψη τοποθεσίας αρχείων κλάσης bootstrap -extdirs Παράκαμψη τοποθεσίας εγκατεστημένων επεκτάσεων -d Καθορίστε πού θα τοποθετήσετε δημιουργημένα αρχεία κλάσης -κωδικοποίηση Καθορίστε κωδικοποίηση χαρακτήρων που χρησιμοποιείται από αρχεία πηγής-στόχος Δημιουργία αρχείων τάξης για συγκεκριμένη έκδοση VM Διαδικασία έξοδος Τιμή: 2 

Ετσι, MediocreExecJavac λειτουργεί και παράγει μια τιμή εξόδου του 2. Κανονικά, μια τιμή εξόδου του 0 δείχνει επιτυχία? οποιαδήποτε μη μηδενική τιμή υποδηλώνει σφάλμα. Η σημασία αυτών των τιμών εξόδου εξαρτάται από το συγκεκριμένο λειτουργικό σύστημα. Ένα σφάλμα Win32 με τιμή 2 είναι σφάλμα "το αρχείο δεν βρέθηκε". Αυτό έχει νόημα, από τότε javac αναμένει να ακολουθήσουμε το πρόγραμμα με το αρχείο πηγαίου κώδικα για μεταγλώττιση.

Έτσι, για να παρακάμψουμε τη δεύτερη παγίδα - κρέμονται για πάντα μέσα Runtime.exec () - εάν το πρόγραμμα που ξεκινάτε παράγει έξοδο ή αναμένει είσοδο, βεβαιωθείτε ότι επεξεργάζεστε τις ροές εισόδου και εξόδου.

Υποθέτοντας μια εντολή είναι ένα εκτελέσιμο πρόγραμμα

Κάτω από το λειτουργικό σύστημα των Windows, πολλοί νέοι προγραμματιστές σκοντάφτουν Runtime.exec () όταν προσπαθείτε να το χρησιμοποιήσετε για μη εκτελέσιμες εντολές όπως σκηνοθέτης και αντίγραφο. Στη συνέχεια, συνάντησαν Runtime.exec ()το τρίτο παγίδα. Η καταχώριση 4.4 δείχνει ακριβώς ότι:

Λίστα 4.4 BadExecWinDir.java

εισαγωγή java.util. *; εισαγωγή java.io. *; δημόσια τάξη BadExecWinDir {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Διαδικασία proc = rt.exec ("dir"); InputStream stdin = proc.getInputStream (); InputStreamReader isr = νέο InputStreamReader (stdin); BufferedReader br = νέο BufferedReader (isr); Γραμμή συμβολοσειράς = null; System.out.println (""); ενώ ((γραμμή = br.readLine ())! = null) System.out.println (γραμμή); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } catch (Throwable t) {t.printStackTrace (); }}} 

Μια σειρά από BadExecWinDir παράγει:

E: \ class \ com \ javaworld \ jpitfalls \ article2> java BadExecWinDir java.io.IOException: CreateProcess: dir error = 2 at java.lang.Win32Process.create (Native Method) στη διεύθυνση java.lang.Win32Process. (Άγνωστη πηγή) στο java.lang.Runtime.execInternal (εγγενής μέθοδος) στο java.lang.Runtime.exec (άγνωστη πηγή) στο java.lang.Runtime.exec (άγνωστη πηγή) στο java.lang.Runtime.exec (άγνωστη πηγή) στο java .lang.Runtime.exec (Άγνωστη πηγή) στο BadExecWinDir.main (BadExecWinDir.java:12) 

Όπως αναφέρθηκε προηγουμένως, η τιμή σφάλματος του 2 σημαίνει "το αρχείο δεν βρέθηκε", το οποίο, σε αυτήν την περίπτωση, σημαίνει ότι το εκτελέσιμο όνομα dir.exe δεν βρέθηκε. Αυτό συμβαίνει επειδή η εντολή καταλόγου είναι μέρος του διερμηνέα εντολών των Windows και όχι ξεχωριστό εκτελέσιμο. Για να εκτελέσετε το διερμηνέα εντολών των Windows, εκτελέστε ένα από αυτά command.com ή cmd.exe, ανάλογα με το λειτουργικό σύστημα των Windows που χρησιμοποιείτε. Η λίστα 4.5 εκτελεί ένα αντίγραφο του διερμηνέα εντολών των Windows και στη συνέχεια εκτελεί την εντολή που παρέχεται από τον χρήστη (π.χ. σκηνοθέτης).

Λίστα 4.5 GoodWindowsExec.java