Teil von SELFHTML aktuell Teil von ArtikelTeil von Java/JSP

Java:
Neuladen von Klassen

nach unten Daniel Thoma
nach unten Hinweise zum Thema
nach unten Beispiel mit Erläuterungen
nach unten Weiterführende Links

Daniel Thoma

E-Mail: E-Mail dthoma@gmx.net

Bei Fragen zu diesem Beitrag bitte den Autor des Beitrags kontaktieren!

nach obennach unten

Hinweise zum Thema

Die Java Virtual Machine (JVM) lädt eine Klasse, wenn sie das erste Mal benötigt wird. Ist die Klasse einmal geladen, wirken sich Änderungen an ihr nicht mehr auf das laufende Programm aus.
Normalerweise ist das sinnvoll, da das erneute Laden Zeit kosten und die Konsistenz der Schnittstellen gefährden würde. Wenn man aber z.B. Plugins verwendet, will man diese meist austauschen oder ändern können ohne die gesamte Anwendung neu starten zu müssen.

Mit einem eigenen ClassLoader kann man erreichen, dass Klassen neu geladen werden. Da Klassen in Java nicht nur über ihr Paket und ihren Namen, sondern auch über ihren ClassLoader identifiziert werden, lassen sich mehrere Versionen einer Klasse laden ohne dass Konflikte auftreten. Man muss lediglich immer, wenn man eine Klasse neu laden will, eine neue Instanz eines ClassLoaders verwenden.

nach obennach unten

Beispiel mit Erläuterungen

Beispiel

<!--%%Datei:Main.java%%-->

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import org.selfhtml.util.ReloadingClassLoader;

/**
 * Beispielprogramm
 */
public class Main {

  /**
   * Hauptroutine
   * @param args Komandozeilenparameter
   * @throws Exception Alle Ausnahmen werden ignoriert
   */
  public static void main(String[] args) throws Exception {
    //Liste für erzeugte Objekte.
    //Ab Java 1.5 sollte man hier ArrayList<Object> objects = new ArrayList<Object>() schreiben.
    ArrayList objects = new ArrayList();
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    String s;
    System.out.print('#');
    //Zeilenweise einlesen der Standardeingabe. Abbrechen wenn nichts eingegeben wird.
    while((s =in.readLine()) != null && !s.equals("")) {
      try {
        //Laden der Klasse mit dem eingegebenen Namen und Instanzieren.
        //Verwenden einer neuen ReloadingClassLoader-Instanz um die Klasse jedes mal neu zu laden.
        objects.add(Class.forName(s, true, new ReloadingClassLoader()).newInstance());

        //Stringdarstellung aller erzeugten Objekte ausgeben.
        for(int a = 0; a < objects.size(); a++) {
          System.out.println(objects.get(a).toString());
        }
      }
      catch(Exception e) {
        System.out.println(e.toString());
      }
      System.out.print('#');
    }
  }
}


<!--%%Datei:test/Test.java%%-->

package test;

/**
 * Einfache Klasse zum Testen, die nicht neu geladen wird.
 */
public class Test {
  public String toString() {
    return "blabla";
  }
}


<!--%%Datei:test/Test2.java%%-->

/**
 * Einfache Klasse zum Testen, die neu geladen wird.
 */
package test;

public class Test2 implements org.selfhtml.util.Reloadable{
  public String toString() {
    return "blabla2";
  }
}


<!--%%Datei:org/selfhtml/util/ReloadingClassLoader.java%%-->

package org.selfhtml.util;

import java.io.InputStream;
import java.util.Arrays;

/**
 * Ein ClassLoader der Klassen für jede seiner Instanzen neu lädt und dazu einen
 * anderen ClassLoader verwendet.
 * @author Daniel Thoma
 * @version 1.0
 */
public class ReloadingClassLoader extends ClassLoader {

  private ClassLoader reloadCL;
  private ClassLoader defaultCL;

  /**
   * Erzeugt eine neue Instanz.
   * Benutzt den System-ClassLoader um die Klassendaten zu laden.
   */
  public ReloadingClassLoader() {
    this(getSystemClassLoader(), getSystemClassLoader());
  }

  /**
   * Erzeugt eine neue Instanz.
   * @param reloadCL ClassLoader, der die Klassendaten von Klassen lädt, die neu geladen werden.
   * @param defaultCL ClassLoader, der die Klassen lädt, die nicht neu geladen werden dürfen.
   */
  public ReloadingClassLoader(ClassLoader reloadCL, ClassLoader defaultCL) {
    super();
    this.reloadCL = reloadCL;
    this.defaultCL = defaultCL;
  }

  /**
   * Gibt den ClassLoader zurück, der die Klassendaten von Klassen lädt, die neu geladen werden.
   * @return Der ClassLoader
   */
  public ClassLoader getReloadClassLoader() {
    return reloadCL;
  }

  /**
   * Gibt den ClassLoader zurück, der die Klassen lädt, die nicht neu geladen werden dürfen.
   * @return Der ClassLoader
   */
  public ClassLoader getDefaultClassLoader() {
    return defaultCL;
  }

  /**
   * Findet die Klasse mit dem angegebenen binären Namen (binary name).
   * (Der Name, wie er von der JVM verwendet wird.)
   * @param name Der binäre Name der Klasse
   * @return Das entsprechende Klassenobjekt
   * @throws ClassNotFoundException falls die Klase nicht gefunden werden konnte
   */
  protected Class findClass(String name) throws ClassNotFoundException {
    byte[] b = loadClassData(name);
    return defineClass(name, b, 0, b.length);
  }

  /**
   * Lädt die Klassendaten der Klasse mit dem angegebenen binären Namen.
   * Benutzt den ClassLoader auf den reloadCL verweist, um die Klassendaten zu finden.
   * @param name Der binäre Name der Klasse
   * @return Die Klassendaten
   * @throws ClassNotFoundException falls die Klasse nicht gefunden werden konnte
   */
  private byte[] loadClassData(String name) throws ClassNotFoundException {
    byte[] data = new byte[0];
    try {
      InputStream in = reloadCL.getResourceAsStream(name.replace('.','/') + ".class");
      byte[] buf = new byte[100];
      int i;
      while((i = in.read(buf)) != -1) {
        byte[] tmp = new byte[data.length + i];
        System.arraycopy(data,0,tmp,0,data.length);
        System.arraycopy(buf,0,tmp,data.length,i);
        data = tmp;
      }
    }
    catch(Exception e) {
      throw new ClassNotFoundException(name);
    }
    return data;
  }

  /**
   * Lädt die Klasse mit dem angegebenen binären Namen.
   * Wenn die Klasse nicht schon von dieser Instanz geladen wurde
   * und Reloadable implementiert, wird sie neu geladen.
   * Implementiert sie die Schnittstelle nicht, wird das Laden an den ClassLoader delegiert,
   * auf den defaultCL verweist.
   * @param name Der binäre Name der Klasse
   * @param resolve Gibt an, ob die Klasse gelinkt werden muss.
   * @return Das entsprechende Klassenobjekt
   * @throws ClassNotFoundException falls die Klase nicht gefunden werden konnte
   */
  protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
    Class c = defaultCL.loadClass(name);
    if(Arrays.asList(c.getInterfaces()).contains(org.selfhtml.util.Reloadable.class)) {
      c = findLoadedClass(name);
      if(c == null) {
        c = findClass(name);
      }
    }
    if (resolve) {
      resolveClass(c);
    }
    return c;
  }
}


<!--%%Datei:org/selfhtml/util/Reloadable.java%%-->

package org.selfhtml.util;

/**
 * Eine Klasse, die diese Schnittstelle implementiert, wird vom ReloadingClassLoader neu geladen.
 */
public interface Reloadable {

}


Erläuterung:

Um das Beispiel auszuprobieren, legen sie innerhalb eines Verzeichnisses die angegebene Verzeichnisstruktur an, kopieren die Dateien hinein und kompilieren sie: javac Main.java test/*.java org/selfhtml/util/*.java

Wenn Sie Java 1.5 oder eine neuere Version verwenden, erhalten Sie einen Warnhinweis (Note: Main.java uses unchecked or unsafe operations.) von dem sie sich nicht weiter irritieren lassen sollten.

Führen Sie das Testprogramm aus: java -cp . Main

Wenn Sie nun den Namen einer Klasse z.B. test.Test2 eingeben und die Eingabetaste drücken wird ein entsprechend Text ausgegeben. Ändern Sie nun den Text in der Klasse test.Test2 und kompilieren Sie sie mit javac test/Test2.java erneut. Brechen Sie das Testprogramm währenddessen nicht ab. Wenn Sie nun erneut bei dem Testprogramm Test2 eingeben, erscheint zuerst der alte und darauf der neue Text: die Klasse wurde also erneut geladen und es existieren nun zwei Versionen der Klasse gleichzeitig.

Wenn sie das selbe mit der Klasse test.Test ausprobieren, können sie diesen Effekt nicht beobachten, da die Klasse org.selfhtml.util.Reloadable nicht implementiert und somit nicht neu geladen wird.

Jede Instanz des ReloadingClassLoader lädt eine Klasse, die org.selfhtml.util.Reloadable implementiert, erneut, auch wenn sie von anderen Instanzen bereits geladen wurde. Dabei wird das Auffinden der Klassendaten an einen angegebenen ClassLoader delegiert.

Implementiert die zu ladende Klasse Reloadable nicht, wird das Laden an einen anderen ClassLoader delegiert. Dieses Verhalten ist notwendig, da die Java Virtual Machine alle Klassen, die eine bestimmte Klasse benötigt über den selben ClassLoader lädt und man diese Klassen nicht unbedingt alle neu laden möchte.

Beachten Sie:

Wenn Sie mehrere Versionen einer Klasse mit unterschiedlichen Schnittstellen verwenden, müssen Sie darauf achten, dass sie keine Methoden aufrufen, die in einer Version gar nicht existieren. Es kann in vielen Fällen sinnvoll sein, mit Hilfe der Reflection API sicher zu stellen, dass die Klasse über die benötigten Methoden verüfgt, von der richtigen Klasse erbt oder die richtigen Interfaces implementiert.

nach obennach unten

Weiterführende Links

Die folgenden Stellen werden empfohlen, um das obige Beispiel besser zu verstehen, oder um weitere Möglichkeiten und Details zu erfahren.

englischsprachige Seite Java API Specification
englischsprachige Seite Java API Specification: java.lang.ClassLoader
englischsprachige Seite Die Java VM Specification zum Laden von Klassen
englischsprachige Seite Die Java Language Specification zum Laden von Klassen

Teil von SELFHTML aktuell Teil von Artikel Teil von Java/JSP

© 2007 bereichsübergreifende Seite Impressum, für diese Seite: E-Mail mail@adresse.x