Java Language
Klassenlader
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
undfindResources
- Ü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.
- Die
loadClass
Methode desloadClass
ruftfindLoadedClass
auf, umfindLoadedClass
, ob eine Klasse mit diesem Namen bereits von diesem Klassenladeprogramm geladen wurde. Wenn dies erfolgreich ist, wird das resultierendeClass
Objekt an den Anforderer zurückgegeben. - Die
loadClass
Methode wird dann an den übergeordneten Classloader delegiert, indem ihrloadClass
Aufruf aufgerufen wird. Wenn die Eltern mit der Bitte umgehen kann, wird es eine RückkehrClass
Objekt , das wird dann an den Anforderer zurückgegeben. - Wenn das übergeordnete Klassenladeprogramm die Klasse nicht laden kann, ruft
findClass
unsere überschreibbarefindClass
Methode auf und übergibt den Namen der zu ladenden Klasse. - Wenn der angeforderte Name mit
this.classname
, rufen wirdefineClass
auf, um die tatsächliche Klasse aus demthis.classfile
defineClass
zu laden. Das resultierendeClass
Objekt wird dann zurückgegeben. - 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();
}