Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5
Java ist auch eine Insel

Java ist auch eine Insel
5., akt. und erw. Auflage
1454 S., mit CD, 49,90 Euro
Galileo Computing
ISBN 3-89842-747-1
gp Kapitel 8 Die Funktionsbibliothek
  gp 8.1 Die Java-Klassenphilosophie
    gp 8.1.1 Übersicht über die Pakete der Standardbibliothek
  gp 8.2 Wrapper-Klassen
    gp 8.2.1 Die Basisklasse Number für numerische Wrapper-Objekte
    gp 8.2.2 Die Klasse Integer
    gp 8.2.3 Wertebereich eines Typs und Überlaufkontrolle
    gp 8.2.4 Unterschiedliche Ausgabeformate
    gp 8.2.5 Autoboxing: Boxing und Unboxing
    gp 8.2.6 Die Character-Klasse
    gp 8.2.7 Die Boolean-Klasse
  gp 8.3 Benutzereinstellungen
    gp 8.3.1 Eine zentrale Registry
    gp 8.3.2 Einträge einfügen, auslesen und löschen
    gp 8.3.3 Auslesen der Daten und Schreiben in anderem Format
    gp 8.3.4 Auf Ereignisse horchen
  gp 8.4 Die Utility-Klasse System
    gp 8.4.1 Systemeigenschaften der Java-Umgebung
    gp 8.4.2 line.separator
    gp 8.4.3 Browser-Version abfragen
    gp 8.4.4 Property von der Konsole aus setzen
    gp 8.4.5 Umgebungsvariablen des Betriebssystems
    gp 8.4.6 Einfache Zeitmessung und Profiling
  gp 8.5 Ausführung von externen Programmen
    gp 8.5.1 Arbeiten mit dem ProcessBuilder
    gp 8.5.2 Die Rückgabe Process übernimmt die Prozesskontrolle
    gp 8.5.3 DOS-Programme aufrufen
    gp 8.5.4 Umgebungsvariablen und Startverzeichnis
    gp 8.5.5 Auf das Ende warten
    gp 8.5.6 Die Windows-Registry verwenden
    gp 8.5.7 Einen HTML-Browser unter Windows aufrufen
  gp 8.6 Klassenlader (Class Loader)
    gp 8.6.1 Woher die kleinen Klassen kommen
    gp 8.6.2 Die wichtigsten drei Typen von Klassenladern
    gp 8.6.3 Der java.lang.ClassLoader
    gp 8.6.4 Hot Deployment mit dem URL-ClassLoader
    gp 8.6.5 Das jre/lib/endorsed-Verzeichnis
    gp 8.6.6 getContextClassLoader() vom Thread
    gp 8.6.7 Wie heißt die Klasse mit der Methode main()?
  gp 8.7 Annotationen
    gp 8.7.1 Die eingebauten Annotations-Typen aus java.lang
    gp 8.7.2 @Deprecated
    gp 8.7.3 Annotationen mit zusätzlichen Informationen
    gp 8.7.4 @SuppressWarnings


Galileo Computing

8.6 Klassenlader (Class Loader)  downtop

Ein Klassenlader ist dafür verantwortlich, eine Klasse zu laden. Aus der Datenquelle (im Allgemeinen eine Datei) liefert der Klassenlader ein Byte-Feld mit den Informationen, die im zweiten Schritt dazu verwendet werden, die Klasse im Laufzeitsystem einzubringen; das nennt sich Linking.

Es gibt eine Reihe von vordefinierten Klassenladern und die Möglichkeit, eigene Klassenlader zu schreiben, um etwa verschlüsselte und komprimierte .class-Dateien zu laden.


Galileo Computing

8.6.1 Woher die kleinen Klassen kommen  downtop

Nehmen wir zu Beginn ein einfaches Programm mit zwei Klassen her:

class A
{
  static String s = new java.util.Date().toString();
  public static void main( String[] args )
  {
    B b = new B();
  }
}
class B
{
  A a;
}

Wenn das Programm A gestartet wird, muss die Laufzeitumgebung einige Klassen laden. Sofort wird klar, dass es zumindest A sein muss. Wenn aber die main()-Funktion aufgerufen wird, muss auch B geladen sein. Und da beim Laden einer Klasse auch die statischen Variablen initialisiert werden, wird auch die Klasse java.util.Date geladen. Zwei weitere Dinge werden nach einiger Überlegung deutlich:

gp  Wenn B geladen wird, bezieht es sich auf A. Da A aber schon geladen ist, muss es nicht noch einmal geladen werden.
gp  Unsichtbar stecken noch andere referenzierte Klassen dahinter, die nicht direkt sichtbar sind. So wird zum Beispiel Object geladen werden, da implizit in der Klassendefinition von A steht: class A extends Object.

Im Beispiel mit den Klassen A und B lädt die Laufzeitumgebung selbstständig die Klassen (implizites Klassenladen). Klassen lassen sich auch mit Class.forName() über ihren Namen laden (explizites Klassenladen).


Hinweis   Um zu sehen, welche Klassen überhaupt geladen werden, kann man der virtuellen Maschine beim Start der Laufzeitumgebung einen Schalter mitgeben – verbose:class. Dann gibt die Maschine beim Lauf alle Klassen aus, die sie lädt.

Die Suchorte

Die Suche nach den Klassen bestimmt ein festes, dreistufiges Schema:

gp  Klassen wie String, Object oder Point stehen in einem ganz speziellen Archiv. Wenn ein eigenes Java-Programm gestartet wird, so sucht die virtuelle Maschine die angeforderten Klassen zuerst in diesem Archiv. Da es elementare Klassen sind, die zum Hochfahren eines Systems gehören, werden sie Bootstrap-Klassen genannt. Das Archiv mit diesen Klassen heißt oft rt.jar (für Run-Time). Andere Archive können hinzukommen – wie i18n.jar, das Internationalisierungsdaten beinhaltet. Die Implementierung dieses Bootstrap-Laders ist nicht öffentlich und wird von System zu System unterschiedlich sein.
gp  Findet die Laufzeitumgebung die Klassen nicht bei den Bootstrap-Klassen, so werden alle Archive eines speziellen Verzeichnisses untersucht, das sich Extension-Verzeichnis nennt. Das Verzeichnis gibt es bei jeder Java-Version und liegt unter lib/ext. Werden hier Klassen eingelagert, so findet es die Laufzeitumgebung ohne weitere Anpassung und Setzen von Pfaden. In sonstigen Verzeichnissen einer Java-Installation sollten keine Klassen kopiert werden.
gp  Ist eine Klasse auch im Erweiterungsverzeichnis nicht zu finden, beginnt die Suche im Klassen-Pfad (Classpath). Diese Pfadangabe besteht aus einer Aufzählung einzelner Verzeichnisse, Klassen oder Jar-Archive, in der die Laufzeitumgebung nach den Klassendateien sucht. Standardmäßig ist dieser Klassenpfad auf das aktuelle Verzeichnis gesetzt (».«).

Setzen des Klassenpfades

Die Suchorte lassen sich angeben, wobei die Bestimmung des Klassenpfades für die eigenen Klassen die wichtigste ist. Sollen in einem Java-Projekt Dateien aus einem Verzeichnis oder externen Java-Archiv geholt werden, so ist der übliche Weg, dieses Verzeichnis oder Archiv im Klassenpfad anzugeben. Dazu gibt es zwei Varianten. Die erste ist, über den Schalter -classpath (kurz -cp) beim Start der virtuellen Maschine die Quellen aufzuführen.

java   -classpath classpath1;classpath2    Ausführklasse

Eine Alternative ist das Setzen der Umgebungsvariablen CLASSPATH mit einer Zeichenfolge, die die Klassen spezifiziert.

SET CLASSPATH=classpath1;classpath2java  Ausführklasse

Ob der Klassenpfad überhaupt gesetzt ist, ermittelt ein einfaches echo $CLASSPATH (Unix) beziehungsweise echo %CLASSPATH% (Windows).


Hinweis   Früher – das heißt vor Java 1.2 – umfasste der CLASSPATH auch die Bootstrap-Klassen. Das ist seit 1.2 überflüssig und bedeutet: Die typischen Klassen aus den Paketen java.*, com.sun.* usw. wie String stehen nicht im CLASSPATH.

Zur Laufzeit steht dieser Klassenpfad in der Umgebungsvariablen java.class.path. Ein println() auf System.getProperty("java.class.path") liefert unter Eclipse mit getrenntem Ordner für Quellcodes und Klassendateien etwa S:\Projekte\workspace\Test\bin.

Auch die Bootstrap-Klassen können angegeben werden. Dazu dient der Schalter -Xbootclasspath oder die Variable sun.boot.class.path. Zusätzliche Erweiterungs-Verzeichnisse lassen sich über die Systemeigenschaft java.ext.dirs zuweisen.


Hinweis   Es gibt spezielle Bootstrap-Klassen, die sich überschreiben lassen. Sie werden in das spezielle Endorsed-Verzeichnis gesetzt. Mehr Informationen dazu weiter unten.


Galileo Computing

8.6.2 Die wichtigsten drei Typen von Klassenladern  downtop

Eine Klassendatei kann von der Java-Laufzeitumgebung über verschiedene Klassenlader bezogen werden. Die wichtigsten sind: Bootstrap-, Erweiterungs- und Applikations-Klassenlader. Sie arbeiten insofern zusammen, als das sie sich gegenseitig Aufgaben zuschieben, wenn eine Klasse nicht gefunden wird.

gp  Bootstrap-Klassenlader: für die Bootstrap-Klassen.
gp  Erweiterungs-Klassenlader: für die Klassen im lib/ext-Verzeichnis.
gp  Applikations-Klassenlader (auch System-Klassenlader): Der letzte Klassenlader im Bunde berücksichtigt bei der Suche den java.class.path.

Aus Sicherheitsgründen beginnt der Klassenlader bei einer neuen Klasse immer mit dem System-Klassenlader und reicht dann die Anfrage weiter, wenn er selbst die Klasse nicht laden konnte. Dazu sind die Klassenlader miteinander verbunden. Jeder Klassenlader L hat dazu einen Vater-Klassenlader V. Erst darf der Vater versuchen, die Klassen zu laden. Kann er es nicht, gibt er die Arbeit an L ab.

Hinter dem letzten Klassenlader können wir einen eigenen Benutzerdefinierten Klassenlader installieren. Auch dieser wird einen Vater haben, den üblicherweise der Applikations-Klassenlader verkörpern wird.


Galileo Computing

8.6.3 Der java.lang.ClassLoader  downtop

Jeder Klassenlader in Java ist vom Typ java.lang.ClassLoader. Die Methode loadClass() erwartet einen so genannten »binären Namen«, der an den voll qualifizierten Klassennamen erinnert.


abstract class java.lang.  ClassLoader  

gp  protected Class<?> loadClass( String name, boolean resolve ) Lädt die Klasse und bindet sie mit resolveClass() ein, wenn resolve gleich true ist.
gp  public Class<?> loadClass( String name ) Die öffentliche Funktion ruft loadClass(name, false) auf, was bedeutet, dass die Klasse nicht standardmäßig angemeldet (gelinkt) wird. Beide Methoden können eine ClassNotFoundException auslösen.

Die geschützte Methode führt anschließend drei Schritte durch:

1. Wird loadClass() auf einer Klasse aufgerufen, die dieser Klassenlader schon eingelesen hat, so kehrt die Funktion mit dieser gecachten Klasse zurück.
       
2. Ist die Klasse nicht gespeichert, darf zuerst der Vater (parent class loader) versuchen, die Klasse zu laden.
       
3. Findet der Vater die Klasse nicht, so darf jetzt der Klassenlader selbst mit findClass()versuchen, die Klasse zu beziehen.
       

Eigene Klassenlader überschreiben in der Regel die Methode findClass(), um nach einem bestimmten Schema zu suchen, etwa nach Klassen aus der Datenbank. In diesen Stufen ist es auch möglich, höher stehende Klassenlader zu umgehen, was beispielsweise bei Servlets Anwendung findet.

Neue Klassenlader

Java nutzt an den verschiedensten Stellen spezielle Klassenlader, etwa für Applets den sun.applet.AppletClassLoader. Für uns ist der java.net.URLClassLoader interessant, da er Klassen von beliebigen URLs laden kann. Wie ein eigener Klassenlader aussieht, zeigt das Beispiel unter http://www.ibiblio.org/javafaq/books/jnp/javanetexamples/10/URLClassLoader.java. Hier wird ein URL-ClassLoader noch einmal nachimplementiert.


Galileo Computing

8.6.4 Hot Deployment mit dem URL-ClassLoader  downtop

Unter »Hot Deployment« ist die Möglichkeit zu verstehen, zur Laufzeit Klassen auszutauschen. Diese Möglichkeit ist für viele EJB- oder Servlet-Container wichtig, da sie im Dateisystem auf eine neue Klassendatei warten und im gegebenen Fall die alte Klasse durch eine neue ersetzen. Im Fall eines Servlet-Containers überwacht er diese Klassen und lädt sie bei Änderungen neu. So etwas gibt es natürlich schon, und eine Internetsuche mit dem Stichwort AdaptiveClassLoader listet Beispiele auf.

Damit dieser heiße Wechsel funktioniert, muss die Klasse über einen neuen Klassenlader bezogen werden. Das liegt daran, dass der Standardklassenlader von Haus aus keine Klasse mehr loswird, wenn er sie einmal geladen hat. Mit anderen Worten: Wenn eine Klasse über Class.forName(Klasse) angefordert wird, ist sie immer im Cache und wird nicht mehr entladen. Ein neuer Klassenlader fängt immer von vorne an, wenn er die Klasse für sich zum ersten Mal sieht.

Mit immer neuen Klassenladern funktioniert das Neuladen, weil für eine neue Klasse dann jeweils ein Klassenlader zuständig ist. Ändert sich die Klasse, wird ein neuer Klassenlader konstruiert, der die neue Klasse lädt. Doch damit ist die alte Klasse noch nicht aus dem Spiel. Nur wenn sich niemand mehr für die alte Klasse und für den Klassenlader interessiert, kann die Laufzeitumgebung diese nicht benutzen Objekte erkennen und aufräumen.

Gleiche Klasse mehrfach laden

Wir wollen im Folgenden eine Funktion newInstance() vorstellen, die beim Aufruf die neueste Version des Dateisystems lädt und ein Exemplar bildet. Die neu zu ladende Klasse soll o.b.d.A. einen Standardkonstruktor haben – andernfalls muss über Reflection ein parametrisierter Konstuktor aufgerufen werden.

Listing 8.11   ClassToLoadMultipleTimes.java

public class ClassToLoadMultipleTimes
{
  static
  {
    System.out.println( "ClassToLoadMultipleTimes" );
  }
}

Jetzt brauchen wir noch eine Testklasse, die ClassToLoadMultipleTimes unter dem Wurzelverzeichnis liest (also etwa unter c:/):

Listing 8.12   LoadClassMultipleTimes.java

import java.io.File;
import java.net.*;
public class LoadClassMultipleTimes
{
  static Object newInstance( String pathString classname ) throws Exception
  {
    URL url = new File( path ).toURI().toURL();
    URLClassLoader cl = new URLClassLoader( new URL[]{ url } );
    Class<?> c = cl.loadClass( classname );
    return c.newInstance();
  }
  public static void main( String[] args ) throws Exception
  {
    newInstance( "/""ClassToLoadMultipleTimes" );
    newInstance( "/""ClassToLoadMultipleTimes" );
  }
}

Zweimal werden wir nun Zeuge, wie die virtuelle Maschine auf der Konsole die Meldung ausgibt, wenn der statische Initialisierungsblock ausgeführt wird.

Implementiert die Klasse eine bestimmte Schnittstelle, oder erbt sie von einer Basisklasse, lässt sich der Typ der Rückgabe unserer Methode newInstance() einschränken. Auf diese Weise ist ein Plugin-Prinzip realisierbar: Die geladene Klasse bietet mit dem Typ Methoden an. Während dieser Typ bekannt ist (der implizite Klassenlader besorgt sie), wird die Klasse selbst erst zur Laufzeit geladen (expliziter Klassenlader).

Die zu ladende Klasse darf nun aber keine Standardklasse (etwa aus java.lang) sein. Das liegt daran, dass auch in dem Fall, in dem die Klasse mit dem eigenen URLClassLoader bezogen werden soll, die Anfrage trotzdem erst an den System-Klassenlader, dann an den Erweiterungs-Klassenlader und erst ganz zum Schluss an unseren eigenen geht. Es ist also aus einem Java-Programm nicht möglich, Klassen zu beziehen, die prinzipiell vom System-Klassenlader geladen werden. Wir können eine Klasse wie javax.swing.JButton nicht selbst beziehen. Wenn sie mit einem Klassenlader ungleich unserem eigenen geladen wird, hat dies wiederum zur Folge, dass wir die geladene Klasse nicht mehr loswerden – was allerdings im Fall der Systemklassen kein Problem sein sollte. Wichtig ist hier, dass der Standardklassenlader die Klasse ClassToLoadMultipleTimes nicht »sehen» darf. Wir müssen die Klasse also zum Beispiel aus dem Pfad löschen, da andernfalls aufgrund des niedrigen Rangs unser eigener URL-Klassenlader nicht zum Zuge kommt.


class java.net.  URLClassLoader
  extends SecureClassLoader

gp  URLClassLoader( URL[] urls ) Erzeugt einen neuen URLClassLoader für ein Feld von URLs mit dem Standard-Vater-Klassenlader.
gp  URLClassLoader( URL[] urls, ClassLoader parent ) Erzeugt einen neuen URLClassLoader für ein Feld von URLs mit einem gegebenen Vater-Klassenlader.
gp  protected void addURL( URL url ) Fügt eine URL hinzu.
gp  URL[] getURLs() Liefert die URLs.

Galileo Computing

8.6.5 Das jre/lib/endorsed-Verzeichnis  downtop

Im Fall der XML-Parser und Bibliotheken kommt es häufiger vor, dass sich die Versionen einmal ändern. Es wäre nun müßig, aus diesem Grund die neuen Bibliotheken, immer im bootclasspath aufzunehmen, da dann immer eine Einstellung über die Kommandozeile stattfinden würde. Die Entwickler haben daher für spezielle Pakete ein Verzeichnis vorgesehen, in denen Updates eingelagert werden können. Das ist das Verzeichnis jre/lib/endorsed der Java-Installation. Alternativ können die Klassen und Archive auch durch die Kommandozeilenoption java.endorsed.dirs spezifiziert werden.

Wenn im endorsed-Verzeichnis eine neue Version – etwa vom XML-Parser – gefunden wird, lädt der Klassenlader die Klassen von dort und nicht aus dem Jar-Archiv, aus dem sonst die Klassen geladen würden, also in der Regel aus rt.jar. Alle gefundenen Klassen überdecken und ergänzen (engl. endorse) somit die Standardklassen aus der Java SE; neue Versionen lassen sich einfach einspielen.

Nicht alle Klassen lassen sich mit endorsed überdecken. Es lässt sich zum Beispiel keine neue Version von java.lang.String einfügen. Die Dokumentation »Endorsed Standards Override Mechanism« zeigt die überschreibbaren Pakete an: javax.rmi.CORBA, org.omg.*, org.w3c.dom, org.xml.*. (Im Übrigen definiert auch Tomcat, der Servlet-Engine, ein solches Überschreib-Verzeichnis. Hier lassen sich Klassen in das common/lib/endorsed-Verzeichnis aufnehmen, die dann beim Start von Tomcat die Standardklassen überschreiben.)


Galileo Computing

8.6.6 getContextClassLoader() vom Thread  downtop

Entwickler von Java Enterprise-Applikationen haben oft damit zu kämpfen, dass immer der falsche Klassenlader eine Klasse bezieht und dann die Typen nicht zusammenpassen. In unserem Beispiel mit dem statischen Initialisierungsblock ist gut zu erkennen, dass er zweimal ausgeführt wird, da ja auch die Klasse zweimal geladen wird. Hätten wir Singletons definiert, würden ihre Anfragemethoden unterschiedliche, nicht kompatible Objekte liefern, obwohl es laut Definition eines Singletons so sein müsste. Allgemein gesprochen: Besonders Fabrikfunktionen liefern bei mehreren Versionen der Klasse unterschiedliche Objekte, die nicht zusammenpassen. Zwei Lösungen gibt es hier: Zum einen bekommen die Fabrikfunktionen einen Klassenlader, in dessen Kontext sie die Klassen erzeugen können, oder sie nutzen den Klassenlader, der mit einem Thread verbunden ist.

Jeder Thread ist mit einem Klassenlader assoziiert, der standardmäßig mit dem Standardklassenlader identisch ist. getContextClassLoader() auf dem Thread-Objekt bezieht diesen Klassenlader:

Listing 8.13   ThreadClassLoader, main()

ClassLoader loader = Thread.currentThread().getContextClassLoader();
System.out.println( loader );  // sun.misc.Launcher$AppClassLoader@a12a00
loader = ThreadClassloader.class.getClassLoader();
System.out.println( loader );  // sun.misc.Launcher$AppClassLoader@a12a00

Soll der assoziierte Klassenlader geändert werden, lässt sich dies mit setContextClassLoader() bewerkstelligen.


Galileo Computing

8.6.7 Wie heißt die Klasse mit der Methode main()?  toptop

In C(++) ist das erste Element des Felds der Funktion main(int argc, char **argv) der Name des Programms. Das ist in Java anders. Die Methode enthält als ersten Parameter nicht den Namen der Klasse beziehungsweise des Programms, sondern einfach den ersten Parameter – sofern auf der Kommandozeile übergeben. Auf einem kleinen Umweg ist das auch für manche Klassen möglich.

Der zu einer Klasse gehörende Klassenlader lässt sich mit dem Class-Objekt erfragen. Mit der Methode getResource() erhalten wir von einem Klassennamen ein URL-Objekt zurück, das dann die Position der Klassendatei im Dateisystem anzeigt. Das folgende Programmbeispiel zeigt, wie wir von einer Klasse den vollständigen Dateipfad zurückbekommen.

Listing 8.14   FindClassPath.java

import java.net.*;
public class FindClassPath
{
  static String getClassPath( Class clazz )
  {
    ClassLoader loader = clazz.getClassLoader();
    if ( loader == null )
      return null;
    URL url = loader.getResource( clazz.getName().replace('.''/' )
                                   + ".class" );
    return ( url != null ) ? url.toString() : null;
  }
  public static void main( String[] args ) throws Exception
  {
    Class c = Class.forName( "FindClassPath" );
    System.out.println( "Klasse: " + c.getName() );
    System.out.println( "Dateiname: " + getClassPath(c) );
  }
}

Unter meinem Dateisystem liefert die Ausgabe:

Klasse: FindClassPath
Dateiname: file:/S:/Private/Comp.Lang.Java/Insel/programme/08_Funktionsbibliothek/FindClassPath.class

Es funktioniert allerdings nur für Klassen, die in kein Jar-Archiv eingebunden sind und nicht den Standardbibliotheken entstammen. Auch ist eine Dateiangabe unmöglich, wenn wir etwa einen eigenen Klassenlader schreiben, der die Klassen aus einer Datenbank bezieht. Dann gibt es keinen Pfad mehr.

getResourceAsStream()

Benötigen wir den Ort einer Klasse, um mit dieser Information auf weitere Dateien im Verzeichnis zuzugreifen, geht es mit der Class.getResourceAsStream(String) einfacher. Diese Methode dient insbesondere dazu, Ressourcen wie Bilder oder Musik aus einem Jar-Archiv auszulesen. Auch der ClassLoader bietet die Methode getResourceAsStream(String) an. Diese Methoden funktionieren ebenfalls für Klassen aus Jar-Archiven, wenn die Ressource auch im Archiv liegt.

 << zurück




Copyright © Galileo Press GmbH 2005
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de