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

Προσθέστε δυναμικό κώδικα Java στην εφαρμογή σας

Το JavaServer Pages (JSP) είναι μια πιο ευέλικτη τεχνολογία από τα servlets επειδή μπορεί να ανταποκριθεί σε δυναμικές αλλαγές κατά το χρόνο εκτέλεσης. Μπορείτε να φανταστείτε μια κοινή κλάση Java που έχει αυτή τη δυναμική ικανότητα; Θα ήταν ενδιαφέρον αν μπορούσατε να τροποποιήσετε την εφαρμογή μιας υπηρεσίας χωρίς να την αναδιοργανώσετε και να ενημερώσετε την εφαρμογή σας εν κινήσει.

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

Ένα παράδειγμα δυναμικού κώδικα Java

Ας ξεκινήσουμε με ένα παράδειγμα δυναμικού κώδικα Java που απεικονίζει τι σημαίνει πραγματικός δυναμικός κώδικας και παρέχει επίσης κάποιο πλαίσιο για περαιτέρω συζητήσεις. Βρείτε τον πλήρη πηγαίο κώδικα αυτού του παραδείγματος στους πόρους.

Το παράδειγμα είναι μια απλή εφαρμογή Java που εξαρτάται από μια υπηρεσία που ονομάζεται Postman. Η υπηρεσία Postman περιγράφεται ως διεπαφή Java και περιέχει μόνο μία μέθοδο, παράδοσηΜήνυμα ():

δημόσια διεπαφή Postman {void deliverMessage (String msg); } 

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

δημόσια τάξη PostmanImpl εφαρμόζει τον Postman {

ιδιωτική έξοδος PrintStream. δημόσιο PostmanImpl () {έξοδος = System.out; } public void deliverMessage (String msg) {output.println ("[Postman]" + msg); έξοδος.flush (); }}

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

δημόσια τάξη PostmanApp {

public static void main (String [] args) ρίχνει την Εξαίρεση {BufferedReader sysin = new BufferedReader (νέο InputStreamReader (System.in));

// Λήψη παρουσίας Postman Postman postman = getPostman ();

while (true) {System.out.print ("Εισαγάγετε ένα μήνυμα:"); Συμβολοσειρά msg = sysin.readLine (); postman.deliverMessage (msg); }}

ιδιωτικό στατικό Postman getPostman () {// Παραλείψτε για τώρα, θα επιστρέψει αργότερα}}

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

[DynaCode] Init class sample.PostmanImpl Πληκτρολογήστε ένα μήνυμα: hello world [Postman] hello world Εισαγάγετε ένα μήνυμα: τι ωραία μέρα! [Ταχυδρόμος] τι ωραία μέρα! Εισαγάγετε ένα μήνυμα: 

Όλα είναι απλά, εκτός από την πρώτη γραμμή, που δείχνει ότι η τάξη Ταχυδρόμος μεταγλωττίζεται και φορτώνεται.

Τώρα είμαστε έτοιμοι να δούμε κάτι δυναμικό. Χωρίς διακοπή της εφαρμογής, ας τροποποιήσουμε ΤαχυδρόμοςΟ πηγαίος κώδικας. Η νέα εφαρμογή παραδίδει όλα τα μηνύματα σε ένα αρχείο κειμένου, αντί της κονσόλας:

// ΤΡΟΠΟΠΟΙΗΜΕΝΗ ΕΚΔΟΣΗ δημόσια τάξη PostmanImpl εφαρμόζει τον Postman {

ιδιωτική έξοδος PrintStream. // Έναρξη τροποποίησης Public PostmanImpl () ρίχνει το IOException {output = new PrintStream (νέο FileOutputStream ("msg.txt")); } // Λήξη τροποποίησης

public void deliverMessage (String msg) {output.println ("[Postman]" + msg);

έξοδος.flush (); }}

Επιστρέψτε στην εφαρμογή και εισαγάγετε περισσότερα μηνύματα. Τι θα συμβεί? Ναι, τα μηνύματα μεταβαίνουν στο αρχείο κειμένου τώρα. Κοιτάξτε την κονσόλα:

[DynaCode] Init class sample.PostmanImpl Πληκτρολογήστε ένα μήνυμα: hello world [Postman] hello world Εισαγάγετε ένα μήνυμα: τι ωραία μέρα! [Ταχυδρόμος] τι ωραία μέρα! Εισαγάγετε ένα μήνυμα: Θέλω να πάω στο αρχείο κειμένου. [DynaCode] Init class sample.PostmanImpl Εισαγάγετε ένα μήνυμα: εγώ κι εγώ! Εισαγάγετε ένα μήνυμα: 

Ειδοποίηση [DynaCode] Init class sample.PostmanImpl εμφανίζεται ξανά, υποδεικνύοντας ότι η τάξη Ταχυδρόμος μεταγλωττίζεται και επαναφορτώνεται. Εάν ελέγξετε το αρχείο κειμένου msg.txt (κάτω από τον κατάλογο εργασίας), θα δείτε τα εξής:

[Ταχυδρόμος] Θέλω να πάω στο αρχείο κειμένου. [Ταχυδρόμος] κι εγώ! 

Καταπληκτικό, σωστά; Μπορούμε να ενημερώσουμε την υπηρεσία Postman κατά το χρόνο εκτέλεσης και η αλλαγή είναι απολύτως διαφανής για την εφαρμογή. (Σημειώστε ότι η εφαρμογή χρησιμοποιεί την ίδια παρουσία Postman για πρόσβαση και στις δύο εκδόσεις των εφαρμογών.)

Τέσσερα βήματα προς τον δυναμικό κώδικα

Επιτρέψτε μου να αποκαλύψω τι συμβαίνει πίσω από τα παρασκήνια. Βασικά, υπάρχουν τέσσερα βήματα για να κάνετε δυναμικό τον κώδικα Java:

  • Αναπτύξτε τον επιλεγμένο πηγαίο κώδικα και παρακολουθήστε τις αλλαγές αρχείων
  • Μεταγλώττιση κώδικα Java κατά το χρόνο εκτέλεσης
  • Φόρτωση / φόρτωση κλάσης Java στο χρόνο εκτέλεσης
  • Συνδέστε την ενημερωμένη τάξη με τον καλούντα

Αναπτύξτε τον επιλεγμένο πηγαίο κώδικα και παρακολουθήστε τις αλλαγές αρχείων

Για να ξεκινήσετε να γράφετε κάποιο δυναμικό κώδικα, το πρώτο ερώτημα που πρέπει να απαντήσουμε είναι, "Ποιο μέρος του κώδικα πρέπει να είναι δυναμικό - ολόκληρη η εφαρμογή ή μόνο μερικές από τις τάξεις;" Τεχνικά, υπάρχουν λίγες περιορισμοί. Μπορείτε να φορτώσετε / φορτώσετε ξανά οποιαδήποτε κλάση Java στο χρόνο εκτέλεσης. Ωστόσο, στις περισσότερες περιπτώσεις, μόνο ένα μέρος του κώδικα χρειάζεται αυτό το επίπεδο ευελιξίας.

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

Για το υπόλοιπο άρθρο, θα κάνουμε τις ακόλουθες παραδοχές σχετικά με τις επιλεγμένες δυναμικές τάξεις:

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

Λάβετε υπόψη ότι αυτές οι παραδοχές δεν αποτελούν προϋποθέσεις. Υπάρχουν μόνο για να κάνουν την υλοποίηση του δυναμικού κώδικα λίγο πιο εύκολη, ώστε να μπορούμε να επικεντρωθούμε περισσότερο στις ιδέες και τους μηχανισμούς.

Λαμβάνοντας υπόψη τις επιλεγμένες δυναμικές τάξεις, η ανάπτυξη του πηγαίου κώδικα είναι μια εύκολη εργασία. Το σχήμα 1 δείχνει τη δομή αρχείων του παραδείγματος Postman.

Γνωρίζουμε ότι το "src" είναι πηγή και το "bin" είναι δυαδικό. Ένα πράγμα που πρέπει να σημειωθεί είναι ο κατάλογος dynacode, ο οποίος διατηρεί τα αρχεία προέλευσης δυναμικών κλάσεων. Εδώ στο παράδειγμα, υπάρχει μόνο ένα αρχείο - PostmanImpl.java. Οι κατάλογοι bin και dynacode απαιτούνται για την εκτέλεση της εφαρμογής, ενώ το src δεν είναι απαραίτητο για ανάπτυξη.

Η ανίχνευση αλλαγών αρχείων μπορεί να επιτευχθεί συγκρίνοντας τις χρονικές σημάνσεις τροποποίησης και τα μεγέθη αρχείων. Για παράδειγμα, πραγματοποιείται έλεγχος στο PostmanImpl.java κάθε φορά που καλείται μια μέθοδος στο Ταχυδρόμος διεπαφή. Εναλλακτικά, μπορείτε να δημιουργήσετε ένα νήμα δαίμονα στο παρασκήνιο για να ελέγχετε τακτικά τις αλλαγές του αρχείου. Αυτό μπορεί να οδηγήσει σε καλύτερη απόδοση για εφαρμογές μεγάλης κλίμακας.

Μεταγλώττιση κώδικα Java κατά το χρόνο εκτέλεσης

Αφού εντοπιστεί μια αλλαγή πηγαίου κώδικα, ερχόμαστε στο ζήτημα της συλλογής. Με την ανάθεση της πραγματικής εργασίας σε έναν υπάρχοντα μεταγλωττιστή Java, η σύνταξη χρόνου εκτέλεσης μπορεί να είναι ένα κομμάτι κέικ. Πολλοί μεταγλωττιστές Java είναι διαθέσιμοι για χρήση, αλλά σε αυτό το άρθρο, χρησιμοποιούμε τον μεταγλωττιστή Javac που περιλαμβάνεται στην πλατφόρμα Java της Sun, Standard Edition (το Java SE είναι το νέο όνομα της Sun για το J2SE).

Στο ελάχιστο, μπορείτε να μεταγλωττίσετε ένα αρχείο Java με μία μόνο δήλωση, υπό την προϋπόθεση ότι το tools.jar, το οποίο περιέχει τον μεταγλωττιστή Javac, βρίσκεται στο classpath (μπορείτε να βρείτε το tools.jar στο

 int errorCode = com.sun.tools.javac.Main.compile (new String [] {"-classpath", "bin", "-d", "/ temp / dynacode_classes", "dynacode / sample / PostmanImpl.java" }); 

Η τάξη com.sun.tools.javac. Κύρια είναι η διεπαφή προγραμματισμού του μεταγλωττιστή Javac. Παρέχει στατικές μεθόδους για την κατάρτιση αρχείων προέλευσης Java. Η εκτέλεση της παραπάνω δήλωσης έχει το ίδιο αποτέλεσμα με την εκτέλεση javac από τη γραμμή εντολών με τα ίδια ορίσματα. Μεταγλωττίζει το αρχείο προέλευσης dynacode / sample / PostmanImpl.java χρησιμοποιώντας τον καθορισμένο κάδο classpath και εξάγει το αρχείο κλάσης στον κατάλογο προορισμού / temp / dynacode_classes. Ένας ακέραιος επιστρέφει ως κωδικός σφάλματος. Μηδέν σημαίνει επιτυχία. οποιοσδήποτε άλλος αριθμός δείχνει ότι κάτι δεν πήγε καλά.

ο com.sun.tools.javac. Κύρια τάξη παρέχει επίσης ένα άλλο συντάσσω() μέθοδος που δέχεται ένα πρόσθετο Εκτύπωση παράμετρος, όπως φαίνεται στον παρακάτω κώδικα. Λεπτομερή μηνύματα σφάλματος θα γραφτούν στο Εκτύπωση εάν η συλλογή αποτύχει.

 // Ορίζεται στο com.sun.tools.javac.Main public static int compile (String [] args); public static int compile (String [] args, PrintWriter out). 

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

Φόρτωση / επαναφόρτωση κλάσης Java στο χρόνο εκτέλεσης

Η μεταγλωττισμένη κλάση πρέπει να φορτωθεί πριν τεθεί σε ισχύ. Η Java είναι ευέλικτη όσον αφορά τη φόρτωση τάξης. Καθορίζει έναν ολοκληρωμένο μηχανισμό φόρτωσης κατηγορίας και παρέχει πολλές υλοποιήσεις των φορτωτών τάξης. (Για περισσότερες πληροφορίες σχετικά με τη φόρτωση τάξης, ανατρέξτε στην ενότητα Πόροι.)

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

// Το dir περιέχει τις μεταγλωττισμένες τάξεις. Κατηγορίες αρχείωνDir = νέο αρχείο ("/ temp / dynacode_classes /");

// Το γονικό classloader ClassLoader parentLoader = Postman.class.getClassLoader ();

// Φόρτωση κλάσης "sample.PostmanImpl" με το δικό μας classloader. URLClassLoader loader1 = νέο URLClassLoader (νέο URL [] {classDir.toURL ()}, parentLoader); Class cls1 = loader1.loadClass ("sample.PostmanImpl"); Ταχυδρόμος postman1 = (Ταχυδρόμος) cls1.newInstance ();

/ * * Επίκληση στον ταχυδρόμο1 ... * Στη συνέχεια, το PostmanImpl.java τροποποιείται και μεταγλωττίζεται ξανά. * /

// Επαναφόρτωση κλάσης "sample.PostmanImpl" με νέο classloader. URLClassLoader loader2 = νέο URLClassLoader (νέο URL [] {classDir.toURL ()}, parentLoader); Class cls2 = loader2.loadClass ("sample.PostmanImpl"); Ταχυδρόμος ταχυδρόμος2 = (Ταχυδρόμος) cls2.newInstance ();

/ * * Εργασία με τον ταχυδρόμο2 από τώρα και στο εξής ... * Μην ανησυχείτε για τους φορτωτές1, cls1 και ταχυδρόμος1 * θα συλλέγονται αυτόματα σκουπίδια. * /

Δώστε προσοχή στο parentLoader κατά τη δημιουργία του δικού σας classloader. Βασικά, ο κανόνας είναι ότι ο γονικός φορτωτής κλάσης πρέπει να παρέχει όλες τις εξαρτήσεις που απαιτεί ο θυγατρικός φορτωτής κλάσης. Έτσι στο δείγμα κώδικα, η δυναμική κλάση Ταχυδρόμος εξαρτάται από τη διεπαφή Ταχυδρόμος; γι 'αυτό χρησιμοποιούμε ΤαχυδρόμοςΤο classloader ως μητρικό classloader.

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

Συνδέστε την ενημερωμένη τάξη με τον καλούντα

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

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

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

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

Το Java αντανάκλαση API περιλαμβάνει ένα εύχρηστο βοηθητικό πρόγραμμα για τη δημιουργία διακομιστών μεσολάβησης. Η τάξη java.lang.reflect.Proxy παρέχει στατικές μεθόδους που σας επιτρέπουν να δημιουργείτε παρουσίες μεσολάβησης για οποιαδήποτε διεπαφή Java.

Το παρακάτω δείγμα κώδικα δημιουργεί έναν διακομιστή μεσολάβησης για τη διεπαφή Ταχυδρόμος. (Εάν δεν είστε εξοικειωμένοι με java.lang.reflect.Proxy, ρίξτε μια ματιά στο Javadoc πριν συνεχίσετε.)

 Χειριστής InvocationHandler = νέο DynaCodeInvocationHandler (...); Postman proxy = (Postman) Proxy.newProxyInstance (Postman.class.getClassLoader (), new Class [] {Postman.class}, χειριστής); 

Οι επιστρεφόμενοι πληρεξούσιο είναι ένα αντικείμενο μιας ανώνυμης κλάσης που μοιράζεται τον ίδιο classloader με το Ταχυδρόμος διεπαφή (το newProxyInstance () πρώτη παράμετρος της μεθόδου) και εφαρμόζει το Ταχυδρόμος διεπαφή (η δεύτερη παράμετρος). Μια επίκληση μεθόδου στο πληρεξούσιο παράδειγμα αποστέλλεται στο χειριστής'μικρό επικαλούμαι() μέθοδος (η τρίτη παράμετρος). Και χειριστήςΗ εφαρμογή μπορεί να έχει την εξής μορφή: