# Falsche Instanz vom Class Loader zurück bekommen



## Marfir (19. Jun 2006)

Hallo,

im Anfängerforum konnte mir niemand helfen. Ich hoffe Ihr habt Ahnung.

Ich habe einen Client den ich wärend der Laufzeit updaten will. Dabei soll mir der Class Loder die neue Version aus dem Internet zurückgeben und anzeigen.
Leider gibt er aber nur eine Instanz meiner lokalen Anwendung zurück. Ich weis aber nicht wieso.  ???:L 


Der Code der das Update startet (wird gleich nach initComponents(); ausgeführt):


```
//neue Version verfügbar
                    int n=0;
                    n=jOptionPane1.showConfirmDialog(this, "Es ist eine neue Version (" + versioninet + ") verfügbar! Wollen Sie Ihre Version jetzt updaten?");
                    if (n==jOptionPane1.YES_OPTION)
                    {
                        ausgabe.append("Programm wird geupdatet. Bitte warten...\n");
                        button1.setEnabled(false);
                        //speichern, dass update bereits durchgeführt wurde
                        BufferedWriter korrigiere = new BufferedWriter(new FileWriter("update.txt"));
                        korrigiere.write("on");
                        korrigiere.close();
                        //auslesen was geupdatet werden soll
                        URL url2 = new URL("http://www.marfir.de/schule/update.txt");
                        URLConnection verbindung2 = url2.openConnection();
                        BufferedReader bfr2 = new BufferedReader(new InputStreamReader(verbindung2.getInputStream()));
                        while ((zeile=bfr2.readLine()) != null)
                        {
                            //neue version laden
                            //Object o = ladeKlasse("http://www.marfir.de/schule/", zeile.trim());
                            Object o = ladeKlasse("file:///E:\\Schule\\Netzwerkprog\\chatclientupdate2\\build\\classes\\", "chatclient");
                            System.out.println(o.toString());
                        }
                        bfr2.close();
                        //fertig geupdatet
                        ausgabe.append("Das Programm wurde erfolgreich aktualisiert!\n");
                        button1.setEnabled(true);
                    }
```


Der Class Loader:


```
static Object ladeKlasse (String pfad, String klassenname) throws Exception
    {
        //klassen laden und neue instanz zurückgeben
        System.out.println(pfad + ", " + klassenname);
        URL url = new URL(pfad);
        URLClassLoader cl = new URLClassLoader(new URL[]{url});
        Class c = cl.loadClass(klassenname);
        System.out.println("isLocalClass? " + c.isLocalClass());
        System.out.println("ladeKlasse(): laden erfolgreich!");
        ausgabe.append("Das Programm ist erfolgreich aktualisiert wurden!\n");
        
        return c.newInstance();
    }
```


Ein Problem wegen Zugriffsrechten o.ä. tritt nicht auf, da es mit einer lokalen Pfadangabe auch nicht funzt. Exceptions gibts auch keine.

Die Ausgabe wenn ich bei der Frage auf ja klicke:

Ausgabe Console:
Chatclient gestartet!
file:///E:\Schule\Netzwerkprog\chatclientupdate2\build\classes\, chatclient
isLocalClass? false
ladeKlasse(): laden erfolgreich!
chatclient[frame0,256,201,511x366,invalid,hidden,layout=java.awt.BorderLayout,title=Chatclient 1.1 mit Updatefunktion © by Marfir,normal,defaultCloseOperation=EXIT_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,0,0x0,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]

Ausgabe Textfeld (ausgabe):
Programm wird geupdatet. Bitte warten...
Das Programm ist erfolgreich aktualisiert wurden!


Wenn alles funktionieren würde, müsste bei chatclient[...title=Chatclient 1.2 oder 2.0 stehen ...]


Hat jemand ne Idee was ich falsch gemacht oder vergessen habe???


----------



## Murray (21. Jun 2006)

M.E. kann das so einfach nicht funktionieren; auch der URLCLassLoader versucht zunächst, die Klasse aus dem Filesystem zu laden (s. Doku zu java.lang.ClassLoader, Stichwort _delegation model_), daher bekommst Du die "alte" Klasse zurück.

BTW: Class.isLocalClass() hat nichts damit zu tun, ob die Klasse aus dem lokalen Filesystem stammt.


----------



## Guest (21. Jun 2006)

Was willst du mir damit jetzt sagen? Wie macht man es denn richtig?


----------



## Murray (21. Jun 2006)

Normalerweise erfordert so ein Software-Update, bei dem Klassen ausgetauscht werden sollen, die bereits geladen sind, dass man die VM beendet und eine neue startet.
Dazu kann man immer mit zwei VMs arbeitet: ein kleiner Loader, der beim Starten ggfs. die Klassen der  eigentlichen Anwendung aktualisiert und diese dann per Runtime#exec( "java -jar myapp.jar") startet.


----------



## Guest (21. Jun 2006)

Aber wenn ich mir die .jar runterlade und starte brauch ich doch keinen Class Loader!


----------



## Lim_Dul (21. Jun 2006)

Es geht auch ohne neustarten.

Dafür musst da aber deinen eigenen ClassLoader schreiben.


```
public class FileClassLoader extends ClassLoader {

  public FileClassLoader() {
    super(null);
  }


  protected Class findClass(String name) throws ClassNotFoundException {
    byte classBytes[]=null;
    String newName = "";
    try {
      newName = name.replace('.', '/');
      System.err.println("Hier bin ich und ich soll folgende Klasse laden: "+newName);
      FileInputStream datei=new FileInputStream(newName+".class");
      classBytes=new byte[datei.available()];
      datei.read(classBytes);
    } catch (IOException e) { throw new ClassNotFoundException(name); }
    return defineClass(name,classBytes,0,classBytes.length);
  }


}
```

Das super(null) steht da, weil er sonst als Parent-Classloader den Systemeigenen Classloader nimmt, was bei mir damals zu Problemen beim Casten auf eine abstrake Oberklasse geführt hat.

Dann kannst du die Klassen laden mittels:


```
Class.forName(name, true,loader);
```

Das sollte sich auch auf URLs anpassen lassen.

Das ganze kann aber auch Probleme bringen, allerdings ist dieses Codestück oben schon etwas älter und ich weiß nicht mehr alles, was ich mir damals gedacht habe.


----------



## Guest (21. Jun 2006)

Irgend wie versteh ich nicht was du da machst Lim_Dul!

Was deine Klasse zurückgibt, damit kann man ja nix anfangen. Was soll ich mit einem Namen, paar Bytes und ner Zahl???
Und den Aufruf versteh ich auch nicht. Was rufst du denn da auf? Und was sind diese 2 anderen Werte (true,loader) da?

Ich brauch doch einen Class Loader der mir ne neue Instanz zurück gibt?!


----------



## Murray (21. Jun 2006)

Lim_Duls FileClassLoader müsstest Du - wie er geschrieben hat - noch erweitern, damit Klassen eben nicht nur aus dem lokalen File-System, sondern auch per URL geladen werden können. Die da implementierte Methode findClass ruft man aber nicht direkt auf; das macht der ClassLoader intern

So einen ClassLoader könntest Du dann in Deiner ladeKlasse-Methode verwenden; entweder so wie Du es getan hast oder wie in Lim_Duls Beispiel mit Class.forName( clsName, true, neuerClassLoader).

Aber: wenn es nur darum geht, den Parent-Class-Loader auf null zu setzen, dann kannst Du das auch mit dem URLClassLoader erreichen; dem kann man als zweiten Parameter im Konstruktor auch einen expliziten Parent-Class-Loader verpassen. Rechne aber damit, dass Du es mit merkwürdigen Effekten zu tun bekommst, wenn Du die von verschiedene ClassLoadern geladenen Klassen bzw. die daraus erzeugten Instanzen vermischt.

Beispiel: Du hast eine Klasse A, in der es eine statische Variable _int nextID_ gibt; der Wert dieser Variablen wird im Konstuktor der (Instanz-)Variablen _id_ zugewiesen und dann inkrementiert (damit ist sichergestellt, dass es in einem Programmlauf niemals zwei Instanzen mit der gleichen ID gibt). Wenn man jetzt noch zwei Klassen B und C dazu denkt, die jeweils von A abgeleitet sind und in ihren Konstruktoren den Konstruktor von A aufrufen (implizit oder explizit), ändert das prinzipiell nichts an der Funktion: sämtliche Instanzen von A,B und C haben immer unterschiedliche IDs.

Wenn jetzt aber B und C von verschiedenen ClassLoader geladen werden, dann laden beide ClassLoader ihre eigene Instanz des Class-Objects für A, daher gibt es dann in der VM die statische(!!) Variable nextID zweimal.

Oder folgendes Phänomen:

```
Object a1 = classLoader1.loadClass( "A").newInstance(); //--- B extends A
Object b1 = classLoader1.loadClass( "B").newInstance(); //--- B extends A
Object c1 = classLoader1.loadClass( "C").newInstance(); //--- C extends A
Object a2 = classLoader2.loadClass( "A").newInstance(); //--- B extends A
Object b2 = classLoader2.loadClass( "B").newInstance(); //--- B extends A
Object c2 = classLoader2.loadClass( "C").newInstance(); //--- C extends A


System.out.println( b1.getClass().getSuperclass() .equals( a1.getClass()); //-- true
System.out.println( c1.getClass().getSuperclass() .equals( a1.getClass()); //-- true
System.out.println( a1.getClass().equals( a2.getClass())); //-- false !!
System.out.println( b2.getClass().getSuperclass() .equals( a1.getClass()); //-- false !!
System.out.println( c1.getClass().getSuperclass() .equals( a2.getClass()); //-- false !!

A a0 = (A)a1; //--- führt zur Laufzeit zu einer Exception (a0.getClass() stammt vom Standard-ClassLoader und ist daher nicht gleich a0.getClass)
```


----------



## Marfir (26. Jun 2006)

Also das mit dem Class Loader hat nicht so richtig geklapt. Das wird mir auch zu kompliziert. Ich hab jetzt einfach die .jar runterladen und starten lassen. Das ist zwar nicht so elegant, aber funzt.   

Danke euch beiden!


----------

