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

Τα βασικά των φορτωτών κλάσης Java

Η έννοια του class loader, ένας από τους ακρογωνιαίους λίθους της εικονικής μηχανής Java, περιγράφει τη συμπεριφορά της μετατροπής μιας ονομασμένης κλάσης σε bit που είναι υπεύθυνα για την εφαρμογή αυτής της κλάσης. Επειδή υπάρχουν φορτωτές τάξης, ο χρόνος εκτέλεσης Java δεν χρειάζεται να γνωρίζει τίποτα για αρχεία και συστήματα αρχείων κατά την εκτέλεση προγραμμάτων Java.

Τι κάνουν οι φορτωτές τάξης

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

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

Κλάση r = loadClass (String className, boolean resolIt); 

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

Όλες οι εικονικές μηχανές Java περιλαμβάνουν ένα φορτωτή κατηγορίας που είναι ενσωματωμένο στην εικονική μηχανή. Αυτός ο ενσωματωμένος φορτωτής ονομάζεται αρχέγονος φορτωτής κατηγορίας. Είναι κάπως ξεχωριστό επειδή η εικονική μηχανή υποθέτει ότι έχει πρόσβαση σε ένα αποθετήριο του αξιόπιστα μαθήματα το οποίο μπορεί να εκτελεστεί από το VM χωρίς επαλήθευση.

Το primordial class loader εφαρμόζει την προεπιλεγμένη εφαρμογή του loadClass (). Έτσι, αυτός ο κωδικός κατανοεί ότι το όνομα της κλάσης java.lang.Object αποθηκεύεται σε ένα αρχείο με το πρόθεμα java / lang / Object.class κάπου στη διαδρομή κλάσης. Αυτός ο κώδικας εφαρμόζει επίσης αναζήτηση διαδρομής κλάσης και αναζήτηση αρχείων zip για τάξεις. Το πολύ ωραίο πράγμα για τον τρόπο που έχει σχεδιαστεί είναι ότι η Java μπορεί να αλλάξει το μοντέλο αποθήκευσης τάξης απλά αλλάζοντας το σύνολο των λειτουργιών που εφαρμόζει τον φορτωτή κλάσης.

Ανατρέχοντας στα έντερα της εικονικής μηχανής Java, θα ανακαλύψετε ότι ο αρχέγονος φορτωτής κλάσης εφαρμόζεται κυρίως στις συναρτήσεις FindClassFromClass και ResolveClass.

Πότε φορτώνονται τα μαθήματα; Υπάρχουν ακριβώς δύο περιπτώσεις: όταν εκτελείται ο νέος bytecode (για παράδειγμα, FooClassφά = νέο FooClass ();) και όταν οι κωδικοί bytec κάνουν μια στατική αναφορά σε μια κλάση (για παράδειγμα, Σύστημα.έξω).

Ένας μη αρχέγονος φορτωτής κατηγορίας

"Και λοιπόν?" μπορεί να ρωτήσετε.

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

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

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

Δημιουργία SimpleClassLoader

Ένας φορτωτής τάξης ξεκινά από την υποκατηγορία του java.lang.ClassLoader. Η μόνη αφηρημένη μέθοδος που πρέπει να εφαρμοστεί είναι loadClass (). Η ροή του loadClass () είναι όπως ακολουθεί:

  • Επαληθεύστε το όνομα της τάξης.
  • Ελέγξτε αν η κλάση που ζητήθηκε έχει ήδη φορτωθεί.
  • Ελέγξτε αν η κλάση είναι κλάση "συστήματος".
  • Προσπάθεια ανάκτησης της κλάσης από το αποθετήριο αυτού του φορτωτή κλάσης.
  • Ορίστε την τάξη για το VM.
  • Επιλύστε την τάξη.
  • Επιστρέψτε την τάξη στον καλούντα.

Το SimpleClassLoader εμφανίζεται ως εξής, με περιγραφές σχετικά με το τι διανέμεται με τον κώδικα.

 δημόσιο συγχρονισμένο Class loadClass (String className, boolean resolIt) ρίχνει ClassNotFoundException {αποτέλεσμα κλάσης; byte classData []; System.out.println (">>>>>> Φόρτωση κλάσης:" + className); / * Ελέγξτε την τοπική κρυφή μνήμη των τάξεων * / result = (Class) pages.get (className); if (αποτέλεσμα! = null) {System.out.println (">>>>>> επιστρέφει προσωρινά αποθηκευμένο αποτέλεσμα."); αποτέλεσμα επιστροφής; } 

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

/ * Ελέγξτε με τον αρχικό αρχικό φορτωτή κλάσης * / δοκιμάστε {result = super.findSystemClass (className); System.out.println (">>>>>> επιστροφή κλάσης συστήματος (σε CLASSPATH)."); αποτέλεσμα επιστροφής; } catch (ClassNotFoundException e) {System.out.println (">>>>>> Όχι μια κλάση συστήματος."); } 

Όπως μπορείτε να δείτε στον παραπάνω κώδικα, το επόμενο βήμα είναι να ελέγξετε αν ο αρχικός αρχιμάγειρας μπορεί να επιλύσει αυτό το όνομα κλάσης. Αυτός ο έλεγχος είναι απαραίτητος τόσο για τη λογική όσο και για την ασφάλεια του συστήματος. Για παράδειγμα, εάν επιστρέψετε τη δική σας παρουσία του java.lang.Object στον καλούντα, τότε αυτό το αντικείμενο δεν θα μοιράζεται κανένα κοινό superclass με κανένα άλλο αντικείμενο! Η ασφάλεια του συστήματος μπορεί να τεθεί σε κίνδυνο εάν ο φορτωτής τάξης σας επέστρεψε τη δική του τιμή java.lang.SecurityManager, που δεν είχαν τους ίδιους ελέγχους με τον πραγματικό.

 / * Προσπαθήστε να το φορτώσετε από το αποθετήριο μας * / classData = getClassImplFromDataBase (className); if (classData == null) {ρίξτε νέο ClassNotFoundException (); } 

Μετά τους αρχικούς ελέγχους, καταλήγουμε στον παραπάνω κωδικό, όπου ο απλός φορτωτής κλάσης έχει την ευκαιρία να φορτώσει μια εφαρμογή αυτής της κλάσης. ο SimpleClassLoader έχει μια μέθοδο getClassImplFromDataBase () το οποίο στο απλό μας παράδειγμα απλώς προθέτει τον κατάλογο "store \" στο όνομα της κλάσης και προσθέτει την επέκταση ".impl". Επέλεξα αυτήν την τεχνική στο παράδειγμα, έτσι ώστε να μην υπάρχει αμφιβολία ότι ο αρχέγονος φορτωτής τάξης θα βρει την τάξη μας. Σημειώστε ότι το sun.applet.AppletClassLoader προθέτει το URL της βάσης κώδικα από τη σελίδα HTML όπου μια μικροεφαρμογή ζει στο όνομα και στη συνέχεια λαμβάνει ένα HTTP αίτημα για ανάκτηση των κωδικών bytec.

 / * Ορίστε το (ανάλυση του αρχείου κλάσης) * / result = defineClass (classData, 0, classData.length); 

Εάν φορτώθηκε η εφαρμογή της τάξης, το προτελευταίο βήμα είναι να καλέσετε το defineClass () μέθοδο από java.lang.ClassLoader, το οποίο μπορεί να θεωρηθεί το πρώτο βήμα της επαλήθευσης τάξης. Αυτή η μέθοδος εφαρμόζεται στην εικονική μηχανή Java και είναι υπεύθυνη για την επαλήθευση ότι τα bytes κλάσης είναι ένα νόμιμο αρχείο κλάσης Java. Εσωτερικά, το defineClass Η μέθοδος συμπληρώνει μια δομή δεδομένων που χρησιμοποιεί το JVM για να κρατήσει τάξεις. Εάν τα δεδομένα κλάσης έχουν λανθασμένη μορφή, αυτή η κλήση θα προκαλέσει α Σφάλμα ClassFormat να πεταχτούν.

 εάν (resolIt) {resolClass (αποτέλεσμα); } 

Η τελευταία απαίτηση για φορτωτή τελευταίας κατηγορίας είναι η κλήση resolClass () εάν η παράμετρος boolean επίλυση ήταν αλήθεια. Αυτή η μέθοδος κάνει δύο πράγματα: Πρώτον, προκαλεί τη φόρτωση οποιωνδήποτε τάξεων που αναφέρονται από αυτήν την τάξη και τη δημιουργία ενός πρωτότυπου αντικειμένου για αυτήν την κλάση. Στη συνέχεια, καλεί τον επαληθευτή να κάνει δυναμική επαλήθευση της νομιμότητας των κωδικών bytec σε αυτήν την κλάση. Εάν η επαλήθευση αποτύχει, αυτή η μέθοδος κλήσης θα ρίξει ένα Σφάλμα Linkage, το πιο κοινό από τα οποία είναι ένα VerifyError.

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

 class.put (className, αποτέλεσμα); System.out.println (">>>>>> Επιστροφή πρόσφατα φορτωμένης κλάσης."); αποτέλεσμα επιστροφής; } 

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

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

Ζητήματα ασφάλειας

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

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

Στο απλό φορτωτή τάξης μπορούμε να προσθέσουμε τον κωδικό:

 εάν (className.startsWith ("java.")) ρίξτε newClassNotFoundException (); 

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

Ένας άλλος τομέας κινδύνου είναι ότι το όνομα που έχει περάσει πρέπει να είναι επαληθευμένο έγκυρο όνομα. Εξετάστε μια εχθρική εφαρμογή που χρησιμοποίησε ένα όνομα κλάσης ".. \ .. \ .. \ .. \ netscape \ temp \ xxx.class" ως το όνομα της κλάσης που ήθελε να φορτωθεί. Σαφώς, εάν ο φορτωτής τάξης απλώς παρουσίασε αυτό το όνομα στον απλοϊκό φορτωτή συστήματος αρχείων μας, αυτό μπορεί να φορτώσει μια κλάση που στην πραγματικότητα δεν αναμενόταν από την εφαρμογή μας. Επομένως, πριν πραγματοποιήσετε αναζήτηση στο δικό μας αποθετήριο τάξεων, είναι καλή ιδέα να γράψετε μια μέθοδο που επαληθεύει την ακεραιότητα των ονομάτων των τάξεων σας. Στη συνέχεια, καλέστε αυτήν τη μέθοδο λίγο πριν πάτε για αναζήτηση στο αποθετήριο σας.

Χρησιμοποιώντας μια διεπαφή για να γεφυρώσετε το κενό

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

 CustomClassLoader ccl = νέο CustomClassLoader (); Αντικείμενο o; Κατηγορία γ; c = ccl.loadClass ("someNewClass"); o = c.newInstance (); ((SomeNewClass) o) .someClassMethod (); 

Ωστόσο, δεν μπορείτε να κάνετε μετάδοση ο προς την SomeNewClass επειδή μόνο ο προσαρμοσμένος φορτωτής κλάσης "γνωρίζει" για τη νέα κλάση που μόλις φόρτωσε.

Υπάρχουν δύο λόγοι για αυτό. Πρώτον, οι κλάσεις στην εικονική μηχανή Java θεωρούνται castable εάν έχουν τουλάχιστον έναν κοινό δείκτη κλάσης. Ωστόσο, οι κατηγορίες που φορτώνονται από δύο διαφορετικούς φορτωτές κατηγορίας θα έχουν δύο διαφορετικούς δείκτες κατηγορίας και δεν υπάρχουν κοινές κατηγορίες (εκτός από java.lang.Object συνήθως). Δεύτερον, η ιδέα της ύπαρξης προσαρμοσμένου φορτωτή κλάσης είναι η φόρτωση κλάσεων μετά η εφαρμογή έχει αναπτυχθεί, ώστε η εφαρμογή να μην γνωρίζει a priory σχετικά με τις τάξεις που θα φορτώσει. Αυτό το δίλημμα επιλύεται δίνοντας τόσο στην εφαρμογή όσο και στην φορτωμένη τάξη μια κοινή κλάση.

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

Το καλύτερο παράδειγμα της πρώτης τεχνικής είναι ένα πρόγραμμα περιήγησης στο Web. Η κλάση που ορίζεται από την Java και εφαρμόζεται από όλες τις μικροεφαρμογές είναι java.applet.Applet. Όταν ένα μάθημα φορτώνεται από AppletClassLoader, η παρουσία αντικειμένου που δημιουργείται μεταφέρεται σε μια παρουσία του Applet. Εάν αυτό το cast πετύχει το μέσα σε αυτό() καλείται μέθοδος. Στο παράδειγμά μου χρησιμοποιώ τη δεύτερη τεχνική, μια διεπαφή.

Παίζοντας με το παράδειγμα

Για να ολοκληρώσω το παράδειγμα που έχω δημιουργήσει μερικά ακόμη

.Ιάβα

αρχεία. Αυτά είναι:

 δημόσια διεπαφή LocalModule {/ * Εκκίνηση της ενότητας * / άκυρη εκκίνηση (επιλογή συμβολοσειράς). }