Suche…


Bemerkungen

Ein Classloader ist eine Klasse, deren Hauptzweck darin besteht, die Position und das Laden der von einer Anwendung verwendeten Klassen zu vermitteln. Ein Klassenlader kann auch Ressourcen finden und laden.

Die Standard-Classloader-Klassen können Klassen und Ressourcen aus Verzeichnissen im Dateisystem und aus JAR- und ZIP-Dateien laden. Sie können JAR- und ZIP-Dateien auch von einem Remote-Server herunterladen und cachen.

Klassenladeprogramme werden normalerweise verkettet, sodass die JVM versucht, Klassen aus den Standardklassenbibliotheken zu bevorzugen, die von der Anwendung bereitgestellten Quellen bevorzugt werden. Benutzerdefinierte Klassenladeprogramme ermöglichen es dem Programmierer, dies zu ändern. Sie können auch Dinge wie das Entschlüsseln von Bytecode-Dateien und die Bytecode-Änderung durchführen.

Instantiieren und Verwenden eines Klassenladers

Dieses grundlegende Beispiel zeigt, wie eine Anwendung einen Classloader instanziieren und verwenden kann, um eine Klasse dynamisch zu laden.

URL[] urls = new URL[] {new URL("file:/home/me/extras.jar")};
Classloader loader = new URLClassLoader(urls);
Class<?> myObjectClass = loader.findClass("com.example.MyObject");

Der in diesem Beispiel erstellte Klassenlader hat den Standardklassenlader als übergeordnetes Element und versucht zuerst, eine Klasse im übergeordneten Klassenlader zu finden, bevor er in "extra.jar" sucht. Wenn die angeforderte Klasse bereits geladen wurde, gibt der findClass Aufruf den Verweis auf die zuvor geladene Klasse zurück.

Der findClass Aufruf kann auf verschiedene Weise fehlschlagen. Die häufigsten sind:

  • Wenn die benannte Klasse nicht gefunden werden kann, wird der Aufruf mit ClassNotFoundException .
  • Wenn die benannte Klasse von einer anderen Klasse abhängt, die nicht gefunden werden kann, gibt der Aufruf NoClassDefFoundError .

Implementieren eines benutzerdefinierten classLoader

Jeder benutzerdefinierte Loader muss die Klasse java.lang.ClassLoader direkt oder indirekt erweitern. Die wichtigsten Erweiterungspunkte sind die folgenden Methoden:

  • findClass(String) - Überladen Sie diese Methode, wenn Ihr Classloader dem Standard-Delegierungsmodell zum Laden von Klassen folgt.
  • loadClass(String, boolean) - Überladen Sie diese Methode, um ein alternatives Delegierungsmodell zu implementieren.
  • findResource und findResources - Überladen Sie diese Methoden, um das Laden von Ressourcen anzupassen.

Die defineClass Methoden, die für das tatsächliche Laden der Klasse aus einem Byte-Array verantwortlich sind, sind final , um ein Überladen zu verhindern. Jedes benutzerdefinierte Verhalten muss vor dem Aufrufen von defineClass .

Hier ist eine einfache Methode, die eine bestimmte Klasse aus einem Byte-Array lädt:

public class ByteArrayClassLoader extends ClassLoader {
    private String classname;
    private byte[] classfile;

    public ByteArrayClassLoader(String classname, byte[] classfile) {
        this.classname = classname;
        this.classfile = classfile.clone();
    }

    @Override
    protected Class findClass(String classname) throws ClassNotFoundException {
        if (classname.equals(this.classname)) {
            return defineClass(classname, classfile, 0, classfile.length);
        } else {
            throw new ClassNotFoundException(classname);
        }
    }
}

Da wir die findClass Methode nur überschrieben haben, findClass dieses benutzerdefinierte Klassenladeprogramm beim loadClass wie folgt.

  1. Die loadClass Methode des loadClass ruft findLoadedClass auf, um findLoadedClass , ob eine Klasse mit diesem Namen bereits von diesem Klassenladeprogramm geladen wurde. Wenn dies erfolgreich ist, wird das resultierende Class Objekt an den Anforderer zurückgegeben.
  2. Die loadClass Methode wird dann an den übergeordneten Classloader delegiert, indem ihr loadClass Aufruf aufgerufen wird. Wenn die Eltern mit der Bitte umgehen kann, wird es eine Rückkehr Class Objekt , das wird dann an den Anforderer zurückgegeben.
  3. Wenn das übergeordnete Klassenladeprogramm die Klasse nicht laden kann, ruft findClass unsere überschreibbare findClass Methode auf und übergibt den Namen der zu ladenden Klasse.
  4. Wenn der angeforderte Name mit this.classname , rufen wir defineClass auf, um die tatsächliche Klasse aus dem this.classfile defineClass zu laden. Das resultierende Class Objekt wird dann zurückgegeben.
  5. Wenn der Name nicht übereinstimmt, wird ClassNotFoundException .

Laden einer externen .class-Datei

Um eine Klasse zu laden, müssen wir sie zuerst definieren. Die Klasse wird vom ClassLoader definiert. Es gibt nur ein Problem: Oracle hat den Code des ClassLoader nicht mit dieser verfügbaren Funktion geschrieben. Um die Klasse zu definieren, müssen wir auf eine Methode namens defineClass() die eine private Methode des ClassLoader .

Um darauf zuzugreifen, erstellen wir eine neue Klasse, ByteClassLoader , und erweitern sie auf ClassLoader . Nun , da wir unsere Klasse erweitert ClassLoader , können wir den Zugriff auf ClassLoader ‚s private Methoden. Um defineClass() verfügbar zu machen, erstellen wir eine neue Methode, die als Spiegel für die private defineClass() -Methode fungiert. Die private Methode nennen wir den Klassennamen, müssen name , die Klasse Bytes, classBytes , das erste Byte des Offset, der sein wird , 0 , weil classBytes 'Daten beginnt bei classBytes[0] , und das letzte Byte des Offset, der sein classBytes.lenght weil es die Größe der Daten darstellt, die der letzte Versatz ist.

public class ByteClassLoader extends ClassLoader {

    public Class<?> defineClass(String name, byte[] classBytes) {
        return defineClass(name, classBytes, 0, classBytes.length);
    }

}

Jetzt haben wir eine öffentliche defineClass() -Methode. Es kann aufgerufen werden, indem der Name der Klasse und die Klassenbytes als Argumente übergeben werden.

Nehmen wir an, wir haben die Klasse MyClass im Paket stackoverflow ...

Um die Methode aufzurufen, benötigen wir die Klassenbytes, sodass wir ein Path Objekt erstellen, das den Path unserer Klasse darstellt. Paths.get() verwenden Sie die Paths.get() Methode und übergeben den Pfad der binären Klasse als Argument. Nun können wir die Klassenbytes mit Files.readAllBytes(path) . Also erstellen wir eine ByteClassLoader Instanz und verwenden die von uns erstellte Methode defineClass() . Wir haben bereits die Klassenbytes, aber zum Aufruf unserer Methode benötigen wir auch den Klassennamen, der durch den Paketnamen (Punkt) und den kanonischen Klassennamen angegeben wird, in diesem Fall stackoverflow.MyClass .

Path path = Paths.get("MyClass.class");

ByteClassLoader loader = new ByteClassLoader();
loader.defineClass("stackoverflow.MyClass", Files.readAllBytes(path);

Anmerkung : Die defineClass() -Methode gibt ein Class<?> Objekt zurück. Sie können es speichern, wenn Sie möchten.

Um die Klasse zu laden, rufen wir einfach loadClass() und übergeben den Klassennamen. Diese Methode kann eine ClassNotFoundException daher müssen wir einen try-Cath-Block verwenden

try{
    loader.loadClass("stackoverflow.MyClass");
} catch(ClassNotFoundException e){
    e.printStackTrace();
}


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow