Java ClassLoader ist eine der entscheidenden, aber selten genutzten Komponenten in der Projektentwicklung. Ich habe ClassLoader in keinem meiner Projekte erweitert. Aber die Idee, meinen eigenen ClassLoader zu haben, der das Laden von Java-Klassen anpassen kann, ist aufregend. Dieser Artikel wird einen Überblick über Java ClassLoader geben und dann einen benutzerdefinierten Klassenlader in Java erstellen.
Was ist Java ClassLoader?
Wir wissen, dass Java-Programme auf der Java Virtual Machine (JVM) laufen. Wenn wir eine Java-Klasse kompilieren, erstellt die JVM den Bytecode, der plattform- und maschinenunabhängig ist. Der Bytecode wird in einer .class-Datei gespeichert. Wenn wir versuchen, eine Klasse zu verwenden, lädt der ClassLoader sie in den Speicher.
Eingebaute ClassLoader-Typen
Es gibt drei Arten von eingebauten ClassLoadern in Java.
- Bootstrap Class Loader – Er lädt interne JDK-Klassen. Er lädt rt.jar und andere Kernklassen, zum Beispiel Klassen des Pakets java.lang.*.
- Extensions Class Loader – Er lädt Klassen aus dem JDK-Erweiterungsverzeichnis, normalerweise das Verzeichnis $JAVA_HOME/lib/ext.
- System Class Loader – Dieser Klassenlader lädt Klassen aus dem aktuellen Klassenpfad. Wir können den Klassenpfad festlegen, wenn wir ein Programm mit der Befehlszeilenoption -cp oder -classpath aufrufen.
Klassenlader-Hierarchie
Der Klassenlader ist hierarchisch aufgebaut, um eine Klasse in den Speicher zu laden. Wenn eine Anforderung zum Laden einer Klasse gestellt wird, delegiert sie dies an den übergeordneten Klassenlader. So wird die Eindeutigkeit in der Laufzeitumgebung gewährleistet. Wenn der übergeordnete Klassenlader die Klasse nicht findet, versucht der Klassenlader selbst, die Klasse zu laden. Dies können wir anhand des folgenden Java-Programms verstehen.
package com.journaldev.classloader;
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println("class loader for HashMap: "
+ java.util.HashMap.class.getClassLoader());
System.out.println("class loader for DNSNameService: "
+ sun.net.spi.nameservice.dns.DNSNameService.class
.getClassLoader());
System.out.println("class loader for this class: "
+ ClassLoaderTest.class.getClassLoader());
System.out.println(com.mysql.jdbc.Blob.class.getClassLoader());
}
}
Ausgabe:
class loader for HashMap: null
class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@7c354093
class loader for this class: sun.misc.Launcher$AppClassLoader@64cbbe37
sun.misc.Launcher$AppClassLoader@64cbbe37
Wie funktioniert der Java ClassLoader?
Lassen Sie uns das Funktionieren der Klassenlader anhand der Ausgabe des obigen Programms verstehen.
- Der java.util.HashMap-Klassenlader ist null, was den Bootstrap-Klassenlader widerspiegelt. Der Klassenlader der DNSNameService-Klasse ist der ExtClassLoader. Da sich die Klasse selbst im CLASSPATH befindet, lädt der System-Klassenlader sie.
- Wenn wir versuchen, HashMap zu laden, delegiert unser System ClassLoader dies an den Extension ClassLoader. Der Erweiterungsklassenlader leitet es an den Bootstrap ClassLoader weiter. Der Bootstrap ClassLoader findet die HashMap-Klasse und lädt sie in den JVM-Speicher.
- Der gleiche Prozess wird für die DNSNameService-Klasse durchgeführt. Der Bootstrap ClassLoader kann sie jedoch nicht finden, da sie sich in
$JAVA_HOME/lib/ext/dnsns.jar
befindet. Daher wird sie vom Extensions Classloader geladen. - Die Blob-Klasse ist in der MySql JDBC Connector-Jar-Datei (mysql-connector-java-5.0.7-bin.jar) enthalten, die sich im Build-Pfad des Projekts befindet. Sie wird auch vom System Classloader geladen.
- Klassen, die von einem untergeordneten Klassenlader geladen werden, haben Sichtbarkeit für Klassen, die von ihren übergeordneten Klassenladern geladen wurden. Klassen, die vom System Classloader geladen werden, haben daher Sichtbarkeit für Klassen, die von Extensions und dem Bootstrap Classloader geladen wurden.
- Wenn es gleichwertige Klassenlader gibt, können sie nicht auf Klassen zugreifen, die von einander geladen wurden.
Warum einen benutzerdefinierten ClassLoader in Java schreiben?
Der Standard-Classloader von Java kann Klassen vom lokalen Dateisystem laden, was in den meisten Fällen ausreichend ist. Wenn jedoch erwartet wird, dass eine Klasse zur Laufzeit oder von einem FTP-Server oder über einen Drittanbieter-Webservice geladen wird, muss der vorhandene Classloader erweitert werden. Zum Beispiel laden AppletViewers die Klassen von einem entfernten Webserver.
Java ClassLoader Methoden
- Wenn die JVM nach einer Klasse verlangt, ruft sie die Funktion
loadClass()
des ClassLoaders auf und übergibt den vollständigen Klassennamen. - Die loadClass()-Funktion ruft die Methode
findLoadedClass()
auf, um zu überprüfen, ob die Klasse bereits geladen wurde oder nicht. Dadurch wird verhindert, dass die gleiche Klasse mehrmals geladen wird. - Wenn die Klasse noch nicht geladen wurde, delegiert sie die Anfrage an den übergeordneten ClassLoader, um die Klasse zu laden.
- Wenn der übergeordnete ClassLoader die Klasse nicht findet, ruft er die Methode findClass() auf, um die Klasse im Dateisystem zu suchen.
Beispiel für einen benutzerdefinierten ClassLoader in Java
Wir werden unseren eigenen ClassLoader erstellen, indem wir die Klasse ClassLoader erweitern und die Methode loadClass(String name) überschreiben. Wenn der Klassenname mit com.journaldev
beginnt, laden wir ihn mit unserem benutzerdefinierten ClassLoader, ansonsten rufen wir die Methode loadClass() des übergeordneten ClassLoader auf, um die Klasse zu laden.
1. CCLoader.java
Dies ist unser benutzerdefinierter ClassLoader mit den folgenden Methoden.
private byte[] loadClassFileData(String name)
: Diese Methode liest die Klassendatei aus dem Dateisystem in ein Byte-Array.private Class<?> getClass(String name)
: Diese Methode ruft die Funktion loadClassFileData() auf und generiert durch Aufrufen der übergeordneten Methode defineClass() die Klasse und gibt sie zurück.public Class<?> loadClass(String name)
: Diese Methode ist für das Laden der Klasse verantwortlich. Wenn der Klassenname mit com.journaldev (unsere Beispieldateien) beginnt, wird er mit der Methode getClass() geladen, ansonsten wird die übergeordnete Funktion loadClass() aufgerufen, um sie zu laden.public CCLoader(ClassLoader parent)
: Dies ist der Konstruktor, der für das Festlegen des übergeordneten ClassLoader verantwortlich ist.
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* Our Custom ClassLoader to load the classes. Any class in the com.journaldev
* package will be loaded using this ClassLoader. For other classes, it will delegate the request to its Parent ClassLoader.
*
*/
public class CCLoader extends ClassLoader {
/**
* This constructor is used to set the parent ClassLoader
*/
public CCLoader(ClassLoader parent) {
super(parent);
}
/**
* Loads the class from the file system. The class file should be located in
* the file system. The name should be relative to get the file location
*
* @param name
* Fully Classified name of the class, for example, com.journaldev.Foo
*/
private Class getClass(String name) throws ClassNotFoundException {
String file = name.replace('.', File.separatorChar) + ".class";
byte[] b = null;
try {
// Dies lädt die Bytecode-Daten aus der Datei
b = loadClassFileData(file);
// defineClass wird von der Klasse ClassLoader vererbt
// die ein Bytearray in eine Klasse umwandelt. defineClass ist final
// daher können wir sie nicht überschreiben
Class c = defineClass(name, b, 0, b.length);
resolveClass(c);
return c;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* Every request for a class passes through this method. If the class is in
* com.journaldev package, we will use this classloader or else delegate the
* request to parent classloader.
*
*
* @param name
* Full class name
*/
@Override
public Class loadClass(String name) throws ClassNotFoundException {
System.out.println("Loading Class '" + name + "'");
if (name.startsWith("com.journaldev")) {
System.out.println("Loading Class using CCLoader");
return getClass(name);
}
return super.loadClass(name);
}
/**
* Reads the file (.class) into a byte array. The file should be
* accessible as a resource and make sure that it's not in Classpath to avoid
* any confusion.
*
* @param name
* Filename
* @return Byte array read from the file
* @throws IOException
* if an exception comes in reading the file
*/
private byte[] loadClassFileData(String name) throws IOException {
InputStream stream = getClass().getClassLoader().getResourceAsStream(
name);
int size = stream.available();
byte buff[] = new byte[size];
DataInputStream in = new DataInputStream(stream);
in.readFully(buff);
in.close();
return buff;
}
}
2. CCRun.java
Dies ist unsere Testklasse mit der main-Funktion. Wir erstellen eine Instanz unseres ClassLoaders und laden Beispielklassen mit seiner loadClass()-Methode. Nach dem Laden der Klasse verwenden wir die Java Reflection API, um ihre Methoden aufzurufen.
import java.lang.reflect.Method;
public class CCRun {
public static void main(String args[]) throws Exception {
String progClass = args[0];
String progArgs[] = new String[args.length - 1];
System.arraycopy(args, 1, progArgs, 0, progArgs.length);
CCLoader ccl = new CCLoader(CCRun.class.getClassLoader());
Class clas = ccl.loadClass(progClass);
Class mainArgType[] = { (new String[0]).getClass() };
Method main = clas.getMethod("main", mainArgType);
Object argsArray[] = { progArgs };
main.invoke(null, argsArray);
// Die folgende Methode wird verwendet, um zu überprüfen, ob Foo geladen wird
// von unserem benutzerdefinierten Klassenlader, d.h. CCLoader
Method printCL = clas.getMethod("printCL", null);
printCL.invoke(null, new Object[0]);
}
}
3. Foo.java und Bar.java
Dies sind unsere Testklassen, die von unserem benutzerdefinierten Klassenlader geladen werden. Sie haben eine printCL()
-Methode, die aufgerufen wird, um Informationen über den Klassenlader zu drucken. Die Klasse Foo wird von unserem benutzerdefinierten Klassenlader geladen. Foo verwendet die Klasse Bar, daher wird auch die Klasse Bar von unserem benutzerdefinierten Klassenlader geladen.
package com.journaldev.cl;
public class Foo {
static public void main(String args[]) throws Exception {
System.out.println("Foo Constructor >>> " + args[0] + " " + args[1]);
Bar bar = new Bar(args[0], args[1]);
bar.printCL();
}
public static void printCL() {
System.out.println("Foo ClassLoader: "+Foo.class.getClassLoader());
}
}
package com.journaldev.cl;
public class Bar {
public Bar(String a, String b) {
System.out.println("Bar Constructor >>> " + a + " " + b);
}
public void printCL() {
System.out.println("Bar ClassLoader: "+Bar.class.getClassLoader());
}
}
4. Ausführungsschritte für Java Custom ClassLoader
Zuallererst werden wir alle Klassen über die Befehlszeile kompilieren. Danach werden wir die Klasse CCRun ausführen und ihr drei Argumente übergeben. Das erste Argument ist der vollständig klassifizierte Name für die Klasse Foo, die von unserem ClassLoader geladen wird. Die anderen beiden Argumente werden an die Hauptfunktion von Foo und den Konstruktor von Bar übergeben. Die Ausführungsschritte und die Ausgabe sehen wie folgt aus.
$ javac -cp . com/journaldev/cl/Foo.java
$ javac -cp . com/journaldev/cl/Bar.java
$ javac CCLoader.java
$ javac CCRun.java
CCRun.java:18: warning: non-varargs call of varargs method with inexact argument type for last parameter;
cast to java.lang.Class<?> for a varargs call
cast to java.lang.Class<?>[] for a non-varargs call and to suppress this warning
Method printCL = clas.getMethod("printCL", null);
^
1 warning
$ java CCRun com.journaldev.cl.Foo 1212 1313
Loading Class 'com.journaldev.cl.Foo'
Loading Class using CCLoader
Loading Class 'java.lang.Object'
Loading Class 'java.lang.String'
Loading Class 'java.lang.Exception'
Loading Class 'java.lang.System'
Loading Class 'java.lang.StringBuilder'
Loading Class 'java.io.PrintStream'
Foo Constructor >>> 1212 1313
Loading Class 'com.journaldev.cl.Bar'
Loading Class using CCLoader
Bar Constructor >>> 1212 1313
Loading Class 'java.lang.Class'
Bar ClassLoader: CCLoader@71f6f0bf
Foo ClassLoader: CCLoader@71f6f0bf
$
Wenn Sie sich die Ausgabe ansehen, versucht sie, die Klasse com.journaldev.cl.Foo
zu laden. Da sie die Java-Klasse java.lang.Object erweitert, versucht sie zuerst, die Klasse Object zu laden. Daher gelangt die Anfrage zur Methode loadClass von CCLoader, die sie an den übergeordneten Klassen delegiert. Daher laden die übergeordneten Klassenlader die Klassen Object, String und andere Java-Klassen. Unser ClassLoader lädt nur die Klassen Foo und Bar aus dem Dateisystem. Dies wird aus der Ausgabe der Funktion printCL() deutlich. Wir können die Funktionalität von loadClassFileData() ändern, um das Byte-Array von einem FTP-Server zu lesen oder durch Aufruf eines Drittanbieterdienstes das Klassenbyte-Array dynamisch zu erhalten. Ich hoffe, dass der Artikel hilfreich ist, um das Arbeiten des Java ClassLoaders zu verstehen und wie wir ihn erweitern können, um viel mehr zu tun als nur Dateien vom Dateisystem zu laden.
Einen benutzerdefinierten Klassenlader als Standard-Klassenlader erstellen
Wir können unseren benutzerdefinierten Klassenlader als Standard-Klassenlader festlegen, wenn die JVM gestartet wird, indem wir Java-Optionen verwenden. Zum Beispiel werde ich das ClassLoaderTest-Programm erneut ausführen, nachdem ich die Java-Klassenladeroption angegeben habe.
$ javac -cp .:../lib/mysql-connector-java-5.0.7-bin.jar com/journaldev/classloader/ClassLoaderTest.java
$ java -cp .:../lib/mysql-connector-java-5.0.7-bin.jar -Djava.system.class.loader=CCLoader com.journaldev.classloader.ClassLoaderTest
Loading Class 'com.journaldev.classloader.ClassLoaderTest'
Loading Class using CCLoader
Loading Class 'java.lang.Object'
Loading Class 'java.lang.String'
Loading Class 'java.lang.System'
Loading Class 'java.lang.StringBuilder'
Loading Class 'java.util.HashMap'
Loading Class 'java.lang.Class'
Loading Class 'java.io.PrintStream'
class loader for HashMap: null
Loading Class 'sun.net.spi.nameservice.dns.DNSNameService'
class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@24480457
class loader for this class: CCLoader@38503429
Loading Class 'com.mysql.jdbc.Blob'
sun.misc.Launcher$AppClassLoader@2f94ca6c
$
Der CCLoader lädt die ClassLoaderTest-Klasse, weil sie sich im Paket com.journaldev
befindet.
Sie können den Beispielcode für den Klassenlader von unserem GitHub-Repository herunterladen.
Source:
https://www.digitalocean.com/community/tutorials/java-classloader