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

BeanLint: Ένα εργαλείο αντιμετώπισης προβλημάτων JavaBeans, Μέρος 1

Κάθε δύο μήνες, λαμβάνω πανικοβλημένα ή μπερδεμένα μηνύματα ηλεκτρονικού ταχυδρομείου από ένα JavaBeans neophyte που προσπαθεί να δημιουργήσει ένα JavaBean που περιέχει Εικόνα και ποιος δεν μπορεί να καταλάβει γιατί το BeanBox δεν θα φορτώσει το φασόλι. Το πρόβλημα είναι ότι java.awt. Εικόνα δεν είναι Σειριοποιήσιμο, επομένως ούτε είναι τίποτα που περιέχει ένα java.awt. Εικόνα, τουλάχιστον χωρίς προσαρμοσμένη σειριοποίηση.

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

Η περίπτωση του φασολιού που λείπει

Ενώ οι απαιτήσεις για να γράψετε μια τάξη Java ως JavaBean είναι απλές και απλές, υπάρχουν κάποιες κρυφές επιπτώσεις που δεν αντιμετωπίζουν πολλά εργαλεία δημιουργίας φασολιών. Αυτά τα μικρά gotchas μπορεί εύκολα να φάει ένα απόγευμα, καθώς κυνηγάτε τον κωδικό σας, αναζητώντας τον λόγο για τον οποίο το εργαλείο κατασκευής δεν μπορεί να βρει το φασόλι σας. Εάν είστε τυχεροί, θα λάβετε ένα αναδυόμενο παράθυρο διαλόγου με ένα κρυπτικό μήνυμα σφάλματος - κάτι σύμφωνα με το "Το NoSuchMethodException πιάστηκε στο FoolTool Introspection"Εάν είστε άτυχος, το JavaBean που έχετε χύσει τόσο πολύ ιδρώτα θα αρνηθεί να εμφανιστεί στο εργαλείο κατασκευής σας και θα περάσετε το απόγευμα προβαίνοντας στο λεξιλόγιο που η μητέρα σας προσπάθησε τόσο σκληρά για να σας θεραπεύσει. Το BeanBox έχει από καιρό ήταν ένας τρομερός παραβάτης από αυτή την άποψη, και παρόλο που έχει βελτιωθεί, θα εξακολουθήσει να πέφτει ιδιότητες και ακόμη και ολόκληρα φασόλια χωρίς να παρέχει στον προγραμματιστή μια μοναδική ένδειξη για το γιατί.

Αυτό το μήνα, θα σας οδηγήσω έξω από τη "γη του φασολιού που λείπει" εισάγοντας ένα νέο εργαλείο που ονομάζεται, περίεργα, BeanLint, η οποία αναλύει τάξεις σε αρχεία βάζων, αναζητώντας πιθανά προβλήματα που θα κάνουν τις τάξεις άχρηστες ως φασόλια. Αν και αυτό το εργαλείο δεν καλύπτει κάθε πιθανό πρόβλημα φασολιών, εντοπίζει ορισμένα από τα κύρια κοινά προβλήματα που κάνουν τα φασόλια να ξεφορτώνονται.

Για να καταλάβουμε πώς BeanLint δουλεύει τη μαγεία του, αυτόν τον μήνα και τον επόμενο θα ερευνήσουμε μερικές από τις λιγότερο γνωστές γωνίες του τυπικού Java API:

  • Θα δημιουργήσουμε ένα έθιμο φορτωτής τάξης, το οποίο φορτώνει νέες κλάσεις Java από ένα αρχείο βάζου

  • Θα χρησιμοποιήσουμε το αντανάκλαση μηχανισμός, που επιτρέπει στα προγράμματα Java να αναλύουν τάξεις Java, για να εντοπίσουν τι υπάρχει μέσα στα αρχεία της τάξης μας

  • Θα χρησιμοποιήσουμε το Ενδοσκόπηση για να δημιουργήσετε μια αναφορά όλων των ιδιοτήτων της κλάσης για τα φασόλια για οποιαδήποτε κλάση στο αρχείο βάζου που περνά όλες τις δοκιμές (και, ως εκ τούτου, είναι ένα πιθανό φασόλι)

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

Βασικά στοιχεία για τα φασόλια

Για να είναι ένα αρχείο κλάσης JavaBean, υπάρχουν δύο απλές απαιτήσεις:

  1. Η τάξη πρέπει να έχει δημόσιο κατασκευαστή χωρίς ορίσματα (α κατασκευαστής zero-arg)

  2. Η τάξη πρέπει να εφαρμόσει τη διεπαφή κενής ετικέτας java.io.Serializable

Αυτό είναι. Ακολουθήστε αυτούς τους δύο απλούς κανόνες και η τάξη σας θα είναι JavaBean. Το απλούστερο JavaBean, λοιπόν, μοιάζει με αυτό:

εισαγωγή java.io. *; δημόσια τάξη Το TinyBean υλοποιεί το Serializable {public TinyBean () {}} 

Φυσικά, το παραπάνω φασόλι δεν είναι καλό για πολλά, αλλά τότε δεν κάναμε πολλή δουλειά σε αυτό. Μόλις δοκιμάστε γράφοντας ένα βασικό συστατικό σαν αυτό σε ένα άλλο πλαίσιο. (Και δεν υπάρχει δίκαιη χρήση "μάγων" ή άλλων δημιουργών κώδικα για τη δημιουργία τάξεων wrapper ή προεπιλεγμένων εφαρμογών. Αυτό δεν είναι δίκαιη σύγκριση της κομψότητας του JavaBeans έναντι άλλης τεχνολογίας.)

ο TinyBean η τάξη δεν έχει ιδιότητες (εκτός, ίσως, "όνομα"), δεν υπάρχουν συμβάντα και καμία μέθοδος. Δυστυχώς, εξακολουθεί να είναι εύκολο να δημιουργήσετε κατά λάθος τάξεις που φαίνεται να ακολουθούν τους κανόνες, αλλά δεν λειτουργούν σωστά σε ένα κοντέινερ JavaBeans όπως το BeanBox ή το αγαπημένο σας IDE (ολοκληρωμένο περιβάλλον ανάπτυξης).

Για παράδειγμα, το BeanBox δεν θα φορτώσει το δικό μας TinyBean παραπάνω εάν ξεχάσαμε να συμπεριλάβουμε τη λέξη-κλειδί δημόσιο στον ορισμό της τάξης. javac θα δημιουργούσε ένα αρχείο τάξης για το μάθημα, αλλά το BeanBox θα αρνηθεί να το φορτώσει και (μέχρι πρόσφατα ούτως ή άλλως) δεν θα έδινε καμία ένδειξη για το γιατί θα το αρνηθεί. Για να δώσει πίστωση στους ανθρώπους της Sun's Java, το BeanBox συνήθως αναφέρει τον λόγο για τον οποίο δεν θα φορτωθεί ένα φασόλι ή τον λόγο για τον οποίο μια ιδιότητα δεν εμφανίζεται σε ένα φύλλο ιδιοκτησίας και ούτω καθεξής. Δεν θα ήταν ωραίο, όμως, αν είχαμε ένα εργαλείο για να ελέγξουμε όσο το δυνατόν περισσότερα πράγματα για τέτοια μαθήματα - και να μας προειδοποιήσει για εκείνους που ενδέχεται να προκαλέσουν προβλήματα όταν χρησιμοποιούνται σε περιβάλλον JavaBeans; Αυτός είναι ο στόχος του BeanLint: για να σας βοηθήσουμε, ως προγραμματιστής JavaBeans, να αναλύσετε φασόλια μέσα στα αρχεία βάζων τους, αναζητώντας πιθανά προβλήματα, ώστε να μπορείτε να τα διορθώσετε προτού τα αντιμετωπίσετε στη διαδικασία δοκιμών ή - ακόμη χειρότερα - στο πεδίο.

Πιθανά προβλήματα φασολιών

Καθώς έχω αναπτύξει JavaBeans για αυτήν τη στήλη, πιθανότατα έκανα τα περισσότερα από τα λάθη που μπορεί να κάνει κάποιος όταν γράφω ένα JavaBean. Κατά κάποιο τρόπο, η σιωπηρή φύση του BeanBox με ανάγκασε να μάθω περισσότερα για τα φασόλια - και για την Java - από ό, τι θα είχα διαφορετικά. Οι περισσότεροι προγραμματιστές JavaBeans, ωστόσο, θα προτιμούσαν απλά να παράγουν λειτουργικά JavaBeans που λειτουργούν σωστά και να αποθηκεύσουν τις «εμπειρίες ανάπτυξης» για την προσωπική τους ζωή. Έχω συλλέξει μια λίστα πιθανών προβλημάτων με ένα αρχείο τάξης που μπορεί να προκαλέσει καταστροφή με ένα JavaBean. Αυτά τα προβλήματα παρουσιάζονται κατά τη διαδικασία φόρτωσης του φασολιού σε ένα δοχείο ή κατά τη χρήση του φασολιού σε μια εφαρμογή. Είναι εύκολο να χάσετε λεπτομέρειες στη σειριοποίηση, επομένως δίνουμε ιδιαίτερη προσοχή στις απαιτήσεις σειριοποίησης.

Ακολουθούν ορισμένα συνηθισμένα προβλήματα που δεν προκαλούν σφάλματα χρόνου μεταγλώττισης, αλλά ενδέχεται να μην προκαλέσουν ένα αρχείο τάξης είναι JavaBean ή να μην λειτουργεί σωστά μόλις φορτωθεί σε κοντέινερ:

  • Η τάξη δεν έχει κατασκευαστή μηδενικών ορισμάτων. Αυτό είναι απλώς μια παραβίαση της πρώτης απαίτησης που αναφέρεται παραπάνω και είναι ένα σφάλμα που δεν συναντάται συχνά από μη αρχάριους.

  • Η τάξη δεν εφαρμόζεται Σειριοποιήσιμο. Αυτό αποτελεί παραβίαση της δεύτερης απαίτησης που αναφέρεται παραπάνω και είναι εύκολο να εντοπιστεί. Μια τάξη μπορεί απαίτηση να εφαρμόσει Σειριοποιήσιμο, και όμως δεν ακολουθεί τη σύμβαση. Σε ορισμένες περιπτώσεις μπορούμε να εντοπίσουμε αυτόματα πότε συνέβη αυτό.

  • Η ίδια η τάξη δεν δηλώνεται δημόσιο.

  • Η τάξη δεν φορτώνεται για κάποιο λόγο. Τα μαθήματα μερικές φορές ρίχνουν εξαιρέσεις καθώς φορτώνονται. Συχνά, αυτό συμβαίνει επειδή άλλες κατηγορίες από τις οποίες εξαρτώνται δεν είναι διαθέσιμες από το ClassLoader αντικείμενο που χρησιμοποιήθηκε για τη φόρτωση της κλάσης. Σε αυτό το άρθρο θα γράφουμε ένα προσαρμοσμένο πρόγραμμα φόρτωσης κατηγορίας (δείτε παρακάτω).

  • Η τάξη είναι αφηρημένη. Ενώ μια κλάση συστατικών, θεωρητικά, θα μπορούσε να είναι αφηρημένη, μια πραγματική παρουσία ενός JavaBean είναι πάντα μια παρουσία συγκεκριμένης (δηλαδή μη αφηρημένης) κλάσης. Οι αφηρημένες τάξεις δεν μπορούν να τεκμηριωθούν, εξ ορισμού, και έτσι δεν θα θεωρήσουμε τις αφηρημένες τάξεις ως υποψήφιες για να είναι φασόλια.

  • Η τάξη υλοποιεί το Serializable, ωστόσο, αυτή ή μία από τις βασικές κατηγορίες της περιέχει μη εναέριου χώρου πεδία. Ο προεπιλεγμένος σχεδιασμός μηχανισμού σειριοποίησης Java επιτρέπει σε μια κλάση να οριστεί ως υλοποιεί το Serializable, αλλά το επιτρέπει να αποτύχει όταν επιχειρείται η σειριοποίηση. Μας BeanLint κλάση διασφαλίζει ότι όλα τα κατάλληλα πεδία του α Σειριοποιήσιμο η τάξη είναι στην πραγματικότητα Σειριοποιήσιμο.

Μια κλάση που αποτυγχάνει σε οποιοδήποτε από τα παραπάνω προβλήματα μπορεί να είναι αρκετά σίγουρη για να μην λειτουργεί σωστά ως JavaBean, ακόμη και αν πληρούνται οι δύο βασικές απαιτήσεις φασολιών, που αναφέρονται στην αρχή. Για κάθε ένα από αυτά τα προβλήματα, λοιπόν, θα ορίσουμε μια δοκιμή που ανιχνεύει το συγκεκριμένο πρόβλημα και το αναφέρει. Στο BeanLint class, οποιοδήποτε αρχείο κλάσης στο αρχείο jar αναλύεται κάνει περάσει όλες αυτές οι δοκιμές είναι τότε ενδοσκόπηση (αναλύθηκε χρησιμοποιώντας την τάξη java.beans.Introspector) για να δημιουργήσετε μια αναφορά για τα χαρακτηριστικά του φασολιού (ιδιότητες, σύνολα συμβάντων, πρόγραμμα προσαρμογής κ.λπ.). java.beans.Introspector είναι μια τάξη στο πακέτο java.beans που χρησιμοποιεί τον μηχανισμό ανάκλασης Java 1.1 για να βρει (ή να δημιουργήσει) α java.beans.BeanInfo αντικείμενο για JavaBean. Θα καλύψουμε τον προβληματισμό και την ενδοσκόπηση τον επόμενο μήνα.

Τώρα ας ρίξουμε μια ματιά στον πηγαίο κώδικα για BeanLint για να δείτε πώς να αναλύσετε πιθανές τάξεις φασολιών.

Παρουσιάζουμε το BeanLint

Στις «παλιές καλές μέρες» (που συνήθως σημαίνει, «όταν ήμουν ακόμα νόμιμος ότι ήξερα τα πάντα»), οι προγραμματιστές C στο λειτουργικό σύστημα Unix θα χρησιμοποιούσαν ένα πρόγραμμα που ονομάζεται στουπί να αναζητήσουν πιθανά σημεία προβλημάτων χρόνου εκτέλεσης στα προγράμματα C τους. Προς τιμήν αυτού του αξιοσέβαστου και χρήσιμου εργαλείου, κάλεσα την τάξη ταπεινής ανάλυσης φασολιών BeanLint.

Αντί να παρουσιάζουμε ολόκληρο τον πηγαίο κώδικα σε ένα τεράστιο, άπεπτο κομμάτι, θα το κοιτάξουμε ένα κομμάτι τη φορά και θα εξηγήσω στην πορεία διάφορα ιδιώματα σχετικά με τον τρόπο με τον οποίο η Java ασχολείται με αρχεία τάξης. Μέχρι τη στιγμή που θα περάσουμε, θα έχουμε γράψει έναν φορτωτή τάξης, θα χρησιμοποιήσουμε έναν αξιοσέβαστο αριθμό τάξεων στο java.lang.reflectκαι έχουν αποκτήσει μια γνωστή γνωριμία με την τάξη java.beans.Introspector. Πρώτα, ας ρίξουμε μια ματιά BeanLint σε δράση για να δούμε τι κάνει, και στη συνέχεια θα ερευνήσουμε τις λεπτομέρειες της εφαρμογής του.

Κακά φασόλια

Σε αυτήν την ενότητα θα δείτε ορισμένα αρχεία τάξης με διάφορα προβλήματα, με το πρόβλημα που υποδεικνύεται κάτω από τον κώδικα. Θα δημιουργήσουμε ένα αρχείο βάζου που περιέχει αυτές τις τάξεις και θα δούμε τι BeanLint κάνει με αυτούς.


εισαγωγή java.io. *;

δημόσια τάξη w υλοποιεί Serializable {w () {}}

Πρόβλημα:

Ο κατασκευαστής μηδενικού ορίσματος όχι

δημόσιο


δημόσια τάξη x {δημόσια x () {}} 

Πρόβλημα:

Δεν

Σειριοποιήσιμο.


εισαγωγή java.io. *;

δημόσια τάξη υλοποιήσεων Serializable {public y (String y_) {}}

Πρόβλημα:

Χωρίς κατασκευαστή μηδενικών ορισμάτων.


εισαγωγή java.io. *;

η κλάση z υλοποιεί το σειριακό περιεχόμενο {public z () {}}

Πρόβλημα:

Η τάξη δεν είναι δημόσια.


εισαγωγή java.io. *; εισαγωγή java.awt. *;

η κλάση u0 υλοποιεί το Serializable {private Image i; δημόσια u0 () {}}

δημόσια τάξη u επεκτείνει τις εφαρμογές u Serializable {public u () {}}

Πρόβλημα:

Περιέχει ένα μη μολυσμένο αντικείμενο ή αναφορά.


εισαγωγή java.io. *;

δημόσια τάξη v επεκτείνει java.awt.Button υλοποιεί Serializable {public v () {} public v (String s) {super (s); }}

Πρόβλημα:

Τίποτα - δεν πρέπει να λειτουργεί καλά!


Κάθε ένα από αυτά τα επίδοξα φασόλια, εκτός από το τελευταίο, έχει πιθανά προβλήματα. Το τελευταίο όχι μόνο είναι ένα φασόλι, αλλά λειτουργεί ως ένα. Μετά τη συλλογή όλων αυτών των τάξεων, δημιουργούμε ένα αρχείο βάζων όπως αυτό:

$ jar cvf BadBeans.jar * .class προσθήκη: u.class (σε = 288) (έξω = 218) (ξεφουσκωμένο 24%) προσθήκη: u0.class (σε = 727) (έξω = 392) (ξεφουσκωμένο 46% προσθέτοντας: w.class (σε = 302) (έξω = 229) (ξεφουσκωμένο 24%) προσθήκη: x.class (σε = 274) (έξω = 206) (ξεφουσκωμένο 24%) προσθήκη: y.class (σε = 362) (έξω = 257) (ξεφουσκωμένο 29%) προσθήκη: z.class (σε = 302) (έξω = 228) (ξεφουσκωμένο 24%) προσθήκη: v.class (σε = 436) (έξω = 285) (ξεφουσκωμένο 34%) 

Δεν πρόκειται να συμπεριλάβουμε ένα αρχείο μανιφέστο (το οποίο είναι ένα αρχείο μέσα σε ένα αρχείο βάζου που περιγράφει τα περιεχόμενα του αρχείου βάζου - δείτε "Άνοιγμα του βάζου" παρακάτω) στο αρχείο βάζου επειδή BeanLint δεν ασχολείται με αρχεία δήλωσης. Η ανάλυση του αρχείου δήλωσης και η σύγκριση του με τα περιεχόμενα του βάζου θα ήταν μια ενδιαφέρουσα άσκηση αν θέλετε να επεκτείνετε αυτό BeanLint μπορώ.

Ας τρέξουμε BeanLint στο αρχείο βάζων και δείτε τι συμβαίνει:

=== Ανάλυση κλάσης u0 === κλάση u0 δεν είναι JavaBean επειδή: η κλάση δεν είναι δημόσια

=== Ανάλυση κλάσης z === κλάση z δεν είναι JavaBean επειδή: η κλάση δεν είναι δημόσια

=== Ανάλυση κλάσης y === κλάση y δεν είναι JavaBean επειδή: δεν έχει κατασκευαστή μηδενικού ορίσματος

=== Ανάλυση κλάσης x === κλάση x δεν είναι JavaBean επειδή: η κλάση δεν είναι σειριοποιήσιμη

=== Ανάλυση κλάσης w === κλάση w δεν είναι JavaBean επειδή: ο κατασκευαστής μηδενικών ορίων δεν είναι δημόσιος

=== Ανάλυση κλάσης v === Σημείωση: Το java.awt.Button ορίζει προσαρμοσμένη σειριοποίηση Σημείωση: Το java.awt.Component καθορίζει την προσαρμοσμένη σειριοποίηση v περνάει όλες τις δοκιμές JavaBean

Αναφορά ενδοσκόπησης -------------------- Κλάση: v Κλάση προσαρμογέα: κανένα

Ιδιότητες: boolean enabled {isEnabled, setEnabled} (... πολλές ακόμη ιδιότητες)

Σύνολα συμβάντων: java.awt.event.MouseListener mouse (... πολλά περισσότερα σύνολα συμβάντων)

Μέθοδοι: public boolean java.awt.Component.isVisible () (... πολλά, Πολλά περισσότερες μέθοδοι - sheesh!)

=== Τέλος κλάσης v ===

=== Ανάλυση κλάσης u === κλάση u δεν είναι JavaBean επειδή: τα ακόλουθα πεδία της κλάσης δεν είναι σειριοποιήσιμα: class java.awt.Image i (ορίζεται σε u0) === Τέλος της κλάσης u ===

Η έξοδος έχει μειωθεί κάπως επειδή οι λίστες των συνόλων και των μεθόδων συμβάντων είναι πολύ μεγάλες δεν προσθέτει πολλά στη συζήτησή μας εδώ. Μπορείτε να δείτε ολόκληρη την έξοδο στο αρχείο output.html, εάν θέλετε μια ιδέα για το ποσό των αντικειμένων BeanLint βάζει έξω.

Σημειώσε ότι BeanLint εντόπισε σωστά τα προβλήματα με τα αρχεία κακής τάξης:

Η κλάση u0 δεν είναι JavaBean επειδή: η κλάση δεν είναι δημόσια κλάση z δεν είναι JavaBean επειδή: η κλάση δεν είναι δημόσια κλάση y δεν είναι JavaBean επειδή: δεν έχει κατασκευαστή μηδενικού ορίσματος η κλάση x δεν είναι JavaBean επειδή: η class δεν είναι Serializable κλάση w δεν είναι JavaBean επειδή: η κατασκευή μηδενικού ορίσματος δεν είναι δημόσια κλάση u δεν είναι JavaBean επειδή: τα ακόλουθα πεδία της κλάσης δεν είναι Serializable: class java.awt.Image i (ορίζεται στο u0)