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

Είναι στη σύμβαση! Εκδόσεις αντικειμένων για JavaBeans

Τους τελευταίους δύο μήνες, έχουμε αναλύσει σε βάθος σχετικά με τον τρόπο σειριοποίησης αντικειμένων στην Java. (Βλέπε "Σειριοποίηση και Προδιαγραφή JavaBeans" και "Κάνε το με τον τρόπο" Nescafé "- με JavaBeans που έχει στεγνώσει με κατάψυξη.") Το άρθρο αυτού του μήνα προϋποθέτει ότι έχετε ήδη διαβάσει αυτά τα άρθρα ή κατανοείτε τα θέματα που καλύπτουν. Πρέπει να καταλάβετε τι είναι η σειριοποίηση, πώς να χρησιμοποιήσετε το Σειριοποιήσιμο διεπαφή και πώς να χρησιμοποιήσετε το java.io.ObjectOutputStream και java.io.ObjectInputStream τάξεις.

Γιατί χρειάζεστε εκδόσεις

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

  • Ένα αρχείο εγγράφου που λάβατε μέσω e-mail δεν θα διαβαστεί σωστά στον επεξεργαστή κειμένου, επειδή η δική σας είναι παλαιότερη έκδοση με ασύμβατη μορφή αρχείου

  • Μια ιστοσελίδα λειτουργεί διαφορετικά σε διαφορετικά προγράμματα περιήγησης επειδή διαφορετικές εκδόσεις προγράμματος περιήγησης υποστηρίζουν διαφορετικά σύνολα δυνατοτήτων

  • Μια εφαρμογή δεν θα εκτελεστεί επειδή έχετε λανθασμένη έκδοση μιας συγκεκριμένης βιβλιοθήκης

  • Το C ++ δεν θα μεταγλωττιστεί επειδή η κεφαλίδα και τα αρχεία προέλευσης είναι μη συμβατές εκδόσεις

Όλες αυτές οι καταστάσεις προκαλούνται από ασύμβατες εκδόσεις λογισμικού ή / και από τα δεδομένα που χειρίζεται το λογισμικό. Όπως τα κτίρια, οι προσωπικές φιλοσοφίες και οι κοίτες του ποταμού, τα προγράμματα αλλάζουν συνεχώς ως απάντηση στις μεταβαλλόμενες συνθήκες γύρω τους. (Εάν δεν πιστεύετε ότι τα κτίρια αλλάζουν, διαβάστε το εξαιρετικό βιβλίο της Stewart Brand Πώς μαθαίνουν τα κτίρια, μια συζήτηση για το πώς μετατρέπονται οι δομές με την πάροδο του χρόνου. Δείτε τους πόρους για περισσότερες πληροφορίες.) Χωρίς δομή για τον έλεγχο και τη διαχείριση αυτής της αλλαγής, οποιοδήποτε σύστημα λογισμικού οποιουδήποτε χρήσιμου μεγέθους τελικά εκφυλίζεται σε χάος. Ο στόχος στο λογισμικό έκδοση είναι να διασφαλίσετε ότι η έκδοση του λογισμικού που χρησιμοποιείτε αυτήν τη στιγμή παράγει σωστά αποτελέσματα όταν συναντά δεδομένα που παράγονται από άλλες εκδόσεις του.

Αυτόν το μήνα, θα συζητήσουμε πώς λειτουργεί η εκδοχή κλάσης Java, ώστε να μπορούμε να παρέχουμε έλεγχο έκδοσης των JavaBeans μας. Η δομή εκδόσεων για κλάσεις Java σάς επιτρέπει να υποδείξετε στον μηχανισμό σειριοποίησης εάν μια συγκεκριμένη ροή δεδομένων (δηλαδή, ένα σειριακό αντικείμενο) είναι αναγνώσιμο από μια συγκεκριμένη έκδοση μιας κλάσης Java. Θα μιλήσουμε για "συμβατές" και "ασυμβίβαστες" αλλαγές σε τάξεις και γιατί αυτές οι αλλαγές επηρεάζουν την έκδοση. Θα εξετάσουμε τους στόχους της δομής εκδόσεων και πώς java.io πακέτο επιτυγχάνει αυτούς τους στόχους. Και, θα μάθουμε να βάζουμε διασφαλίσεις στον κώδικά μας για να διασφαλίσουμε ότι όταν διαβάζουμε ροές αντικειμένων διαφόρων εκδόσεων, τα δεδομένα είναι πάντα συνεπή μετά την ανάγνωση του αντικειμένου.

Εκτροπή έκδοσης

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

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

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

  • Οι μέθοδοι και τα πεδία μιας τάξης πρέπει να διατηρούν το ίδιο νόημα με την εξέλιξη της τάξης, διαφορετικά τα υπάρχοντα προγράμματα ενδέχεται να σπάσουν σε μέρη όπου χρησιμοποιούνται αυτές οι μέθοδοι και πεδία

  • Ο πηγαίος κώδικας, τα αρχεία κεφαλίδας, η τεκμηρίωση και τα σενάρια build πρέπει να συντονίζονται σε ένα περιβάλλον δημιουργίας λογισμικού για να διασφαλιστεί ότι τα δυαδικά αρχεία δημιουργούνται από τις σωστές εκδόσεις των αρχείων προέλευσης

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

Είναι σημαντικό να θυμάστε ότι οι σειριακές ροές αντικειμένων Java δεν περιέχουν bytecodes. Περιέχουν μόνο τις απαραίτητες πληροφορίες για την ανακατασκευή ενός αντικειμένου υποθέτοντας έχετε τα αρχεία κλάσης διαθέσιμα για τη δημιουργία του αντικειμένου. Αλλά τι θα συμβεί εάν τα αρχεία κλάσης των δύο εικονικών μηχανών Java (JVMs) (ο συγγραφέας και ο αναγνώστης) έχουν διαφορετικές εκδόσεις; Πώς ξέρουμε αν είναι συμβατά;

Ένας ορισμός τάξης μπορεί να θεωρηθεί ως «σύμβαση» μεταξύ της τάξης και του κωδικού που καλεί την τάξη. Αυτή η σύμβαση περιλαμβάνει το μάθημα API (διεπαφή προγραμματισμού εφαρμογών). Η αλλαγή του API ισοδυναμεί με την αλλαγή της σύμβασης. (Άλλες αλλαγές σε μια τάξη μπορεί επίσης να συνεπάγονται αλλαγές στη σύμβαση, όπως θα δούμε.) Καθώς μια τάξη εξελίσσεται, είναι σημαντικό να διατηρηθεί η συμπεριφορά των προηγούμενων εκδόσεων της τάξης, ώστε να μην διακοπεί το λογισμικό σε μέρη που εξαρτώνται από δεδομένη συμπεριφορά.

Ένα παράδειγμα αλλαγής έκδοσης

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

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

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

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

Συμβατές και ασύμβατες αλλαγές

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

Οι σχεδιαστές του μηχανισμού σειριοποίησης για την Java είχαν κατά νου τους ακόλουθους στόχους όταν δημιούργησαν το σύστημα:

  1. Για να ορίσετε έναν τρόπο με τον οποίο μια νεότερη έκδοση μιας τάξης μπορεί να διαβάσει και να γράψει ροές που μια προηγούμενη έκδοση της τάξης μπορεί επίσης να "κατανοήσει" και να χρησιμοποιήσει σωστά

  2. Να παρέχει έναν προεπιλεγμένο μηχανισμό που σειριοποιεί αντικείμενα με καλή απόδοση και λογικό μέγεθος. Αυτό είναι το μηχανισμός σειριοποίησης έχουμε ήδη συζητήσει στις δύο προηγούμενες στήλες JavaBeans που αναφέρονται στην αρχή αυτού του άρθρου

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

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

Ας δούμε πώς ο μηχανισμός σειριοποίησης αντιμετωπίζει αυτούς τους στόχους υπό το φως της κατάστασης που περιγράφεται παραπάνω.

Συμφιλίωση διαφορών

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

Οι ταξεις java.io.ObjectInputStream και java.io.ObjectOutputStream δεν σε εμπιστεύομαι. Είναι σχεδιασμένα να είναι, από προεπιλογή, εξαιρετικά ύποπτα για τυχόν αλλαγές στη διεπαφή ενός αρχείου τάξης στον κόσμο - που σημαίνει, οτιδήποτε είναι ορατό σε οποιαδήποτε άλλη τάξη που μπορεί να χρησιμοποιεί την κλάση: τις υπογραφές των δημόσιων μεθόδων και διεπαφών και τους τύπους και τους τροποποιητές δημόσιων τομέων. Είναι τόσο παρανοϊκοί, στην πραγματικότητα, που δεν μπορείτε να αλλάξετε σχεδόν τίποτα για μια τάξη χωρίς να προκαλέσετε java.io.ObjectInputStream να αρνηθείτε να φορτώσετε μια ροή γραμμένη από προηγούμενη έκδοση της τάξης σας.

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

001 002 εισαγωγή java.beans. *; 003 εισαγωγή java.io. *; 004 εισαγωγή εκτυπώσιμη; 005 006 // 007 // Έκδοση 1: απλώς αποθηκεύστε την ποσότητα στο χέρι και τον αριθμό ανταλλακτικού 008 // 009 010 δημόσια κατηγορία InventoryItem implement Serializable, Printable {011 012 013 014 015 016 // πεδία 017 protected int iQuantityOnHand_; 018 προστατευμένο String sPartNo_; 019 020 δημόσιο απόθεμαItem () 021 {022 iQuantityOnHand_ = -1; 023 sPartNo_ = ""; 024} 025 026 δημόσιο InventoryItem (String _sPartNo, int _iQuantityOnHand) 027 {028 setQuantityOnHand (_iQuantityOnHand); 029 setPartNo (_sPartNo); 030} 031 032 δημόσια int getQuantityOnHand () 033 {034 return iQuantityOnHand_; 035} 036 037 public void setQuantityOnHand (int _iQuantityOnHand) 038 {039 iQuantityOnHand_ = _iQuantityOnHand; 040} 041 042 δημόσια συμβολοσειρά getPartNo () 043 {044 return sPartNo_; 045} 046 047 public void setPartNo (String _sPartNo) 048 {049 sPartNo_ = _sPartNo; 050} 051 052 // ... υλοποιεί εκτυπώσιμη 053 δημόσια άκυρη εκτύπωση () 054 {055 System.out.println ("Part:" + getPartNo () + "\ n Ποσότητα στο χέρι:" + 056 getQuantityOnHand () + "\ n \ n "); 057} 058}; 059 

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

C: \ beans> java Demo8a w αρχείο SA0091-001 33 Γραπτό αντικείμενο: Μέρος: SA0091-001 Ποσότητα στο χέρι: 33 C: \ beans> java Demo8a r αρχείο Διαβάστε το αντικείμενο: Μέρος: SA0091-001 Ποσότητα στο χέρι: 33 

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

016 // πεδία 017 προστατευμένα int iQuantityOnHand_; 018 προστατευμένο String sPartNo_; 019 δημόσιο int iQuantityLost_; 

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

C: \ mj-java \ Στήλη8> java Demo8a r αρχείο IO Εξαίρεση: InventoryItem; Τοπική κλάση δεν είναι συμβατή java.io.InvalidClassException: InventoryItem; Τοπική κλάση δεν είναι συμβατή στο java.io.ObjectStreamClass.setClass (ObjectStreamClass.java:2319) στο java.io.ObjectInputStream.inputClassDescriptor (ObjectInputStream.java:639) at java.io.ObjectInputStream.readObject (ObjectInputStream.java java.io.ObjectInputStream.inputObject (ObjectInputStream.java:820) στο java.io.ObjectInputStream.readObject (ObjectInputStream.java ::84) στο Demo8a.main (Demo8a.java:56) 

Ω, φίλε! Τι συνέβη?

java.io.ObjectInputStream δεν γράφει αντικείμενα κλάσης όταν δημιουργεί μια ροή byte που αντιπροσωπεύει ένα αντικείμενο. Αντ 'αυτού, γράφει ένα java.io.ObjectStreamClass, το οποίο είναι ένα περιγραφή της τάξης. Ο φορτωτής κλάσης προορισμού JVM χρησιμοποιεί αυτήν την περιγραφή για να βρει και να φορτώσει τους κωδικούς bytec για την κλάση. Δημιουργεί επίσης και περιλαμβάνει έναν ακέραιο αριθμό 64-bit που ονομάζεται a SerialVersionUID, το οποίο είναι ένα είδος κλειδιού που προσδιορίζει μοναδικά μια έκδοση αρχείου κλάσης.

ο SerialVersionUID δημιουργείται με τον υπολογισμό ενός ασφαλούς κατακερματισμού 64-bit των παρακάτω πληροφοριών σχετικά με την τάξη. Ο μηχανισμός σειριοποίησης θέλει να είναι σε θέση να εντοπίσει την αλλαγή σε οποιοδήποτε από τα ακόλουθα πράγματα:

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