![]() |
![]() |
|
![]() |
![]() |
Home Contact Links English |
|
Java: Classloading in Standard-Java und OSGi-UmgebungenDer AusgangspunktHaben Sie vielleicht schon einmal während der Ausführung eines Java-Programms eine Fehlermeldung gesehen die besagte, dass das Objekt des Typs "X" nicht auf ein Objekt des Typs "X" gecasted werden kann. Natürlich prüften Sie sofort, ob die package-Namen abweichen, aber sie waren identisch. Welches Problem könnte die JVM (Java Virtual Machine) haben? Nun, das Problem ist, dass unterschiedliche Classloader den Typ "X" geladen haben. Zur Laufzeit wird eine Klasse oder ein Interface durch folgende Attribute identifiziert:
Es gibt viele Wege eine Klasse zu laden. Der new-Operator lädt die Klasse, bevor er sie instanziiert. Ein anderer Weg ist die Benutzung von Class.forName(). Wie auch immer, beide Aufrufe führen zum Aufruf der loadClass-Methode des aktuellen Classloaders (dies ist derjenige, der zur Erzeugung der aktuellen this-Klasse benutzt wurde, von dem aus der aktuelle Aufruf ausgeht). Um den Classloader zu ermitteln, kann this.getClass().getClassLoader() aufgerufen werden, der normalerweise den System-Classloader zurück gibt (falls der Classloader nicht z.B. durch ein unterlegtes OSGi-Framework bereits geändert wurde). Classloader-HierarchieJava besitzt eine eingebaute Classloader-Hierarchie. Obwohl diese Hierarchie in vielen Fällen (wie z.B. bei OSGi, EJB ...) nicht mehr benutzt wird, benutzen alle neueren Technologien dieses grundlegende Konzept. Das folgende Bild zeigt den Ladevorgang der Klasse "Worker", die in "Worker.class" kompiliert und dann unter Umständen in eine jar-Datei verpackt wurde. Zuerst prüft der System-Classloader seinen Zwischenspeicher (Cache), ob er bereits die Klasse geladen hat. Falls nein, dann besteht der nächste Schritt immer darin, den jeweiligen Parent-Classloader aufzurufen. Dies geschieht so ebenfalls in den elterlichen Classloadern, bis der Bootstrap-Classloader erreicht ist. Der Bootstrap-Classloader, der auch als "primordial classloader" bezeichnet wird, ist der oberste Knoten der Classloader-Hierarchie. Er ist oft Teil der JVM-Implementation und deswegen keine Java-Klasse, sondern eher ein logischer Part. Der Bootstrap-Classloader durchsucht nun alle class- und jar-Dateien im Pfad "/jre/lib" in <JAVA_HOME>. Der vorherige hierarchische Suchalgorithmus stellt sicher, dass gemeinsame Klassen (wie z.B. java.util.Hashtable) immer vom selben Classloader geladen werden. Dies ist der Grund, warum man mit solchen grundlegenden Klassen praktisch nie cast-Probleme hat. In unserem Beispiel kann der Bootstrap-Classloader die Klasse "Worker" nicht laden. Also kehrt der Aufruf zum Extension-Classloader zurück, der nun seinerseits alle class- und jar-Dateien im Pfad "/jre/etx/lib" in <JAVA_HOME> durchsucht. Der Extension-Classloader prüft zwar ebenfalls jedes Directory, das durch das System-Property "java.ext.dirs" angegeben ist, aber das soll hier keine weitere Rolle spielen. Wiederum wurde die Klasse "Worker" nicht gefunden. Also kehrt der Aufruf zum System-Classloader zurück. Dieser untersucht nun den Klassenpfad (classpath), der die zu durchsuchenden class- und jar-Dateien angibt. Es gibt mehrere Beitragende, die den Klassenpfad ergänzen. Zu aller erst natürlich die CLASSPATH-Umgebungsvariable. Dann sind da noch die Kommandozeilen-Optionen "-classpath" und "-cp". Und wenn eine jar-Datei im Klassenpfad ein Manifest mit einem "Class-Path"-Attribut besitzt, dann werden die durch dieses Attribut spezifizierten jar-Dateien ebenfalls durchsucht. An einem dieser Orte sollte der System-Classloader unsere "Worker"-Klasse finden. Classloading in OSGiEine Frage wurde noch nicht gestellt ... warum bietet Java einen derart komplizierten Mechanismus zum Laden von Klassen? Die Antwort darauf lautet, dass dieses Feature dringend von vielen Anwendungen benötigt wird - so zum Beispiel auch von Frameworks wie OSGi. Eine der größten Errungenschaften von OSGi ist, ein Framework anzubieten, das voneinander isolierte Komponenten erlaubt, indem ihre Klassen isoliert werden. So ist es sogar möglich, mehrere Versionen ein und derselben Komponente zur gleichen Zeit ausführen zu lassen. Dazu sei noch einmal die "Worker"-Klasse von oben herangezogen. Wir könnten eine solche Klasse in einer ersten Version unserer Komponente implementieren. Aber nach einiger Zeit könnte es notwendig werden, eine neue Version von "Worker" zu entwickeln - vielleicht für eine neue Komponente. In dieser Situation ist es wirklich notwendig, sorgfältig zwischen den Versionen zur Laufzeit zu unterscheiden. Und wahrscheinlich wäre es keine gute Sache in der Lage zu sein, eine Instanz der ersten Version von "Worker" auf die zweite Version von "Worker" zu casten und umgekehrt. Das obige Bild zeigt die Situation im Detail. In dem Kontext von OSGi werden die Komponenten als "Bundles" bezeichnet. Jedes Bundle besitzt seinen eigenen Classloader. Falls zum Beispiel Bundle 1 die Klasse "Worker" laden würde, dann wird sie ihre manifest-Datei zu Rate ziehen. Diese ist im Pfad "/META-INF/MANIFEST.MF" innerhalb ihrer eigenen jar-Datei abgelegt. Wenn nun Bundle 2 ebenfalls die Klasse "Worker" lädt, dann wird sie in ihrem eigenen jar nachschauen. So sind die Klassen der Bundles komplett voneinander getrennt. Selbst wenn Bundle 1 versuchen würde eine Instanz von "Worker" an das Bundle 2 weiterzugeben, würde das nicht helfen; denn für Bundle 2 hat diese "Worker"-Klasse nichts mit ihrer eigenen "Worker"-Klasse zu tun. Und selbst wenn der Classloader von Bundle 1 aus derselben jar-Datei lesen würde wie Bundle 2, würde sich daran nichts ändern, weil nach wie vor die Klassen immer noch von verschiedenen Classloadern geladen würden. Dabei mag eine andere Frage auftauchen ... wenn die Klassen der Bundles derart perfekt voneinander isoliert sind, wie können dann zwei Bundles über gemeinsame Klassen zusammen arbeiten? Die Antwort liegt im Manifest. Dort können diese Restriktionen gelockert werden, indem z.B. entsprechende "Import-Package:"- und "Export-Package:"-Anweisungen hinzugefügt werden. Weitere Informationen sind auf der Seite OSGi/RCP zu finden. Apropos ... es ist möglich, den elterlichen Classloader der Bundles zu ändern. Dazu gibt es ein Java-Property namens "osgi.parentClassloader" das die folgenden Werte unterstützt:
Copyright (c) 2016: Jürgen Luers |