# geänderte Klassen nachladen, ohne JVM neu zu starten



## haeppchen (11. Okt 2005)

Hallo zusammen,

ich habe folgendes Problem:
ich starte eine Java-GUI und wähle mehrere .java-Dateien aus, deren Inhalt ich um einige Informationen erweitere; ich schreibe also neue Zeilen in die .java-Dateien hinein.

Anschließend will ich diese jetzt „infizierten“ .java-Dateien ausführen.
Dazu brauche ich das Class-Objekt zu diesen .java-Dateien. An sich kein Problem, denn ich weiß ja, wo diese „infizierten“ Java-Dateien liegen.

Das Problem ist aber, dass ich beim Erzeugen der Class-Objekte zu diesen .java-Dateien nur das „alte“ Class-Objekt der .java-Dateien bekomme, also das Class-Objekt zu dem Zeitpunkt, als ich meine GUI gestartet habe und die Dateien noch nicht infiziert waren..
Ich verwende dabei einen ClassLoader, der die zu suchende Klasse neu lädt (loadClass (String name, boolean resolve) mit resolve = true). Trotzdem wird nur die alte Version der Klasse gefunden.

Wie kann ich an das aktuelle Class-Objekt kommen, also an das Class-Objekt der bereits infizierten Dateien?
Wenn ich zwischen dem Infizieren der Dateien und dem Ausführen die Anwendung neu starte, habe ich die aktuellen Class-Objekte. Dass liegt wohl daran, dass die Java Virtual Machine neu gestartet wird (dass will ich aber unbedingt vermeiden, weil es nicht sehr elegant ist )


Wer kann bei diesem Problem helfen??


----------



## Bleiglanz (11. Okt 2005)

>>Ich verwende dabei einen ClassLoader

erzeuge einfach eine neue Instanz von diesem ClassLoader, der wird dann die neuen Klassen laden


----------



## haeppchen (14. Okt 2005)

Hallo Bleiglanz,

danke für deine Hilfe.
Ich habe versucht, deinen Ansatz in die Tat umzusetzen und habe folgende Methode geschrieben, die eine geänderte Klasse nachladen soll.
Wäre nett, wenn du mal kurz drüberguckst, die Methode funktioniert leider nicht einwandfrei:


private Object neuesExemplar(String pfad, String klassename) throws Exception
	{		
	    URL url = new File(pfad).toURL();
                    // Parent-Classloader mit null initialisieren, um die Klasse neuzuladen
	    /* +++ */ URLClassLoader cl = new URLClassLoader( new URL[]{ url }, null);
	    Class c = cl.loadClass(klassename);
	    return c.newInstance();
	}


Getestet habe ich wie folgt:

Ich habe eine Test-Klasse auf Festplatte, die ich ausführe und die ein einfaches System.out.print(1) macht.
An das Class-Objekt komme ich mit Class.forName() problemlos dran, weil diese Datei zum Startzeitpunkt der JVM ja schon vorhanden ist.
Nachdem ich diese Test-Klasse ausgeführt habe, ändere ich den Inhalt der Test-Klasse, so dass sie beim erneuten Ausführen ein System.out.print(2) machen soll.

Nach dem Ändern versuche ich zunächst an das Class-Objekt dieser geänderten Klasse zu kommen bzw. ich versuche, die geänderte Klasse nachzuladen mit oben erwähnter Methode neuesExemplar().
Leider erhalte ich eine ClassNotFoundException in der mit +++ gekennzeichneten Zeile.

Hast du eine Idee, was ich falsch mache?

Gruß Simon


----------



## Bleiglanz (14. Okt 2005)

lass die null weg

new URLClassLoader( new URL[]{ url }); 

und eine ClassNotFoundEx an DER Stelle? Wirklich?


----------



## haeppchen (14. Okt 2005)

Hallo Bleiglanz,

wenn ich das null weglasse, findet er die neue Klasse aber nicht (habe ich schon getestet).
Er findet dafür die alte Version der Datei und startet diese ohne Exception, aber halt die ALTE Datei!!

In diesem Topic http://forum.java.sun.com/thread.jspa?forumID=31&threadID=671964 hat mir ein User extra geschrieben, dass ich mit null den Parent ClassLoader anspreche, der dafür sorgt, dass die Klassen neu geladen werden.

Ich werde dort auch nochmal mein Problem schildern.
Irgendwie trete ich auf der Stelle, dabei muss das dynamische Nachladen von Klassen ohne extra Neu-Start der JVM doch möglich sein!!


----------



## Bleiglanz (14. Okt 2005)

komisch

ist der Ordner wo die .class Datei drin ist etwa im Classpath deiner JVM?


----------



## Mag1c (14. Okt 2005)

Hi,

hast du den Pfad zu den Klassen, die du nachladen willst, im Classpath beim Start der Anwendung drin ? Wenn ja, nimm den dort mal raus. Wenns dann nicht mehr funktioniert (das 1. Laden der Klasse) dann stimmt was mit dem eigenen ClassLoader noch nicht und dann kann auch das Nachladen nicht funktionieren.

Gruß
Mag1c


----------



## haeppchen (14. Okt 2005)

Ja, ich habe den Pfad zu den Klassen, die ich nachladen will, im Class-Path.
Dann könnte es ja vielleicht klappen, wenn ich den Pfad aus dem Class-Path rausnehme??
Werde das versuchen!!


----------



## Bleiglanz (14. Okt 2005)

ja

der Parent sucht ja immer zuerst...


----------



## haeppchen (18. Okt 2005)

Hallo,

leider hat sich unser Problem mit dem Nachladen von Klassen trotz eurer Hilfe noch nicht lösen lassen,
daher habe ich ein kleines Testprogramm gepostet, das die Problematik verdeutlichen soll.
Ich wäre sehr dankbar, wenn ihr euch das kleine Testprogramm mal kurz anschaut


Wir haben eine Testdatei.java, die als einzige Methode die main-Methode enthält. 
Ich hole mir das Class-Objekt dieser Datei und mittels Reflection die deklarierten Methoden dieser Klasse.
Klappt alles prima.

Anschließend ändere ich den Dateiinhalt der Testdatei.java mit der Methode aendere(). Diese Methode schreibt in die Testdatei.java einfach eine zusätzliche Methode a() rein. 

Danach lösche ich die alte .class-Datei und compiliere die Testadatei.java neu (mit compileJava).
Jetzt hole ich mir wieder das Class-Objekt dieser geänderten Datei und will mir mittels Reflection erneut die Methoden ausgeben lassen. Eigentlich müssten mir jetzt die main()-Methode und die Methode a angezeigt werden, aber leider wird mir nur die main()-Methode angezeigt. Vermutlich wurde die geänderte Klasse nicht nachgeladen!!


---------------------------------------------Programm----------------------------------------------

*---------------------------------------------Code-Tags----------------------------------------------*


```
package testpackage;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import com.sun.tools.javac.Main;


public class LoadClass 
{	
	private File f;
	private Class c = null;
	private Object o;
	
	public LoadClass()
	{       
		f = new File("C:/AAJ/src/testpackage/Testdatei.java");
		
		try 
		{
			o = neuesExemplar(f.getPath(), "testpackage.Testdatei");
			c = o.getClass();
			System.out.println("Class " + c.getName() + " found.");
		} 
		catch (Exception e) 
		{e.printStackTrace();}

		Method [] method = c.getDeclaredMethods();
		for (int i=0; i<method.length; i++)
		{
			System.out.println(method[i].getName());
		}
		
		aendere();		
		compileJava(f.getPath());
		
		File fileToDelete = new File("bin/testpackage/Testdatei.class");
		System.out.println(fileToDelete.delete());

		
		Class cla = null;
		
		try 
		{
			Object o = neuesExemplar(f.getPath(), "testpackage.Testdatei");
			cla = o.getClass();
			System.out.println("Class " + cla.getName() + " found.");
		} 
		catch (Exception e) 
		{e.printStackTrace();}

		Method [] methods = cla.getDeclaredMethods();
		for (int i=0; i<methods.length; i++)
		{
			System.out.println(methods[i].getName());
		}
				
	}
	
	
	private int compileJava(String javaFile) 
	{
	  String[] args = {"-d", "C:/AAJ/bin/", "-classpath", System.getProperty("java.class.path"), javaFile};
	  
	  return Main.compile(args);
	}
	
	
	public static void main(String[] args) 
	{
		new LoadClass();
	}
	

	private Object neuesExemplar(String pfad, String klassename) throws Exception
	{		
	    URL url = new File(pfad).toURL();
	    URLClassLoader cl = new URLClassLoader( new URL[]{ url });
	    Class c = cl.loadClass(klassename);
	    return c.newInstance();
	}
	 
	
	private void aendere()
	{	 
		FileWriter fw;
		BufferedWriter bw;
		
		try 
		{
			fw = new FileWriter(f);
			bw = new BufferedWriter(fw);			
			bw.write("package testpackage;");
			bw.newLine();
			bw.newLine();
			bw.write("public class Testdatei");
			bw.newLine();
			bw.write("{");
			bw.newLine();
			bw.write("public static void main (String [] args)");
			bw.newLine();
			bw.write("{");
			bw.write("System.out.println(4);");
			bw.newLine();
			bw.write("}");
			bw.newLine();
			bw.newLine();
			bw.write("private void a ()");
			bw.newLine();
			bw.write("{");
			bw.newLine();
			bw.write("System.out.println(55);");
			bw.newLine();
			bw.write("}");
			bw.newLine();
			bw.write("}");
			bw.close();
			fw.close();			
		} 
		catch (IOException e) 
		{e.printStackTrace();}									
	 }

}
```

*---------------------------------------------Code-Tags----------------------------------------------*


---------------------------------------------Programm----------------------------------------------




Ich nutze Eclipse in der Version 3.1.0. Kann es sein, dass Eclipse diese Probleme verursacht, dass Eclipse die alte .class-Datei vom Anfang im Speicher hält und dann immer wieder verwendet?



Vielen Dank im Voraus

Simon


----------



## Bleiglanz (18. Okt 2005)

könnte an Eclipse liegen: src/ ist ja wohl im Classpath...

bei einer Klasse, die im Classpath ist und also vom SystemClassLoader geladen wird geht das nicht

weil immer der Parent zuerst zuschlägt und dieser die Klasse schon geladen hat kann man so eine klasse nicht neu laden, dann müsstest du wieder die (..,null) parent=null methode wählen


----------



## Mag1c (18. Okt 2005)

Hi,

nein, src ist nicht im Classpath. Aber "bin" ist, wenn man es nicht ändert, der "Default output folder" und damit ganz sicher im Classpath. Du solltest mal für die nachzuladende Klasse ein ganz anderes Verzeichnis wählen.

Gruß
Mag1c


----------



## haeppchen (18. Okt 2005)

Hallo Bleiglanz!

.../src liegt nicht im Classpath. Dort habe ich lediglich den Pfad für mein JDK gesetzt.

Die parent=null Methode wirft Exceptions in der Methode "neuesExemplar" in der Zeile Class c=cl.loadClass(klassenname).

Hast du noch weitere Tipps?


----------



## Mag1c (18. Okt 2005)

Hi,

ja, ist klar, warum. Du benutzt als Pfad für den URLClassLoader den Pfad zur Sourcedatei Testdatei.java. Das kann nicht gehen. Du mußt da den Pfad zur kompilierten Klasse angeben ... und zwar ohne Package. In deinem Fall wäre das "C:/AAJ/bin" (oder der geänderte Pfad).

Gruß
Mag1c


----------



## haeppchen (19. Okt 2005)

Hallo Bleiglanz und Mag1c,

ich habe mein Problem mit dem dynamischen Nachladen von Klassen endlich
beheben können. Ich lasse die geänderten Klassen neu kompilieren und "packe" die
.class-Dateien in ein Verzeichnis, das nicht im Classpath liegt. Weiterhin verwende ich die Methode neues Exemplar mit dem parent-Classloader, also

URLClassLoader cl = new URLClassLoader(new URL[]{ url }, *null*); 

und die geänderten Klassen sind da!!
Danke bis hierhin für eure tolle Unterstützung.

Leider ist ein neues (aber vermutlich kleineres) Problem aufgetaucht.
Das Erzeugen einer neuen Instanz mit c.newInstance() führt zu einer InstantiationException, wenn ich class-Dateien nachlade, die eine Abstrakte Klasse oder ein Interface darstellen. Diese Exception ist für mich auch logisch und vermutlich kann ich dieses Problem auch umgehen.

Bei folgender Beispiel-Klasse kriege ich aber ebenfalls eine InstantiationException und ich weiß nicht warum:

Hier die Struktur der Klasse:

----------------------------------------------------------------
public class A extends class1 implements interface1
{
     class B extends class 2 {}
}

class C extends class3 implements interface3 {}

class D extends class4 implements interface4 {}

----------------------------------------------------------------

Habt ihr eine Idee, woran die Exception liegen könnte??
Oder wie ich allgemein beim Nachladen einer Klasse mit c.newInstance() derartige Exceptions abfangen?

Wäre euch für eure Hilfe nochmal sehr dankbar!



Gruß simon


----------



## KSG9|sebastian (19. Okt 2005)

Andere Frage: Hast du es geschaft, dass du mehrere Java-Dateien kompilieren kannst.
Ich hab mal bissl mit Main.compile rumgespielt aber hatte immer folgendes Problem:



```
public class Test1{
  public Test2 t2;
}
```


```
public class Test2{
  public Test1 t1;
}
```

Da hat er immer gesagt.. "Cannot resolve symbol". Hast du das hinbekommen ?


----------



## Mag1c (19. Okt 2005)

Hi,

also ich blick da nicht durch. Was sind das alles für Klassen und Interfaces ? Wo liegen die ? Werden die auch recompiliert ?

Gruß
Mag1c


----------



## KSG9|sebastian (19. Okt 2005)

also.....das ganze läuft so:

- in nem Ordner "src" liegen Java-Dateien drin
- Programm startet, alle .java-Dateien aus dem Ordner "src" werden kompiliert
- alle kompilierten Klassen aus Punkt 2 werden nachgeladen
- Im Ordner src wird an der Datei "Test1.java" etwas geändert
- Test1.class fliegt aus raus aus dem ClassLoader
- Test1.java wird kompiliert
- Test1.class wird mittels CL wieder geladen


----------



## Bleiglanz (19. Okt 2005)

haeppchen hat gesagt.:
			
		

> Leider ist ein neues (aber vermutlich kleineres) Problem aufgetaucht.
> Das Erzeugen einer neuen Instanz mit c.newInstance() führt zu einer InstantiationException, wenn ich class-Dateien nachlade, die eine Abstrakte Klasse oder ein Interface darstellen. Diese Exception ist für mich auch logisch und vermutlich kann ich dieses Problem auch umgehen.
> 
> Bei folgender Beispiel-Klasse kriege ich aber ebenfalls eine InstantiationException und ich weiß nicht warum:
> ...



a) natürlich kriegst du mit newInstance keine Instanzen von abstrakten Klassen oder Interfaces - woher sollten die auch kommen!!

b) dein neuer schöner Classloader kennt natürlich keine fremden Klassen (interace1 2 3), die möglicherweise ein anderer CL geladen hat - ohne Parent musst du also alles was benötigt wird selbst bereitstellen

c) bei newInstance() solltest du immer in einem try-catch machen und ein "catch(Exception e)" mit dazupacken, sonst fliegen nämlich eventuell sogar checked-Exceptions aus deiner Methode raus, obwohl die gar nicht deklariert wurden...


----------



## haeppchen (19. Okt 2005)

Hallo Bleiglanz! Sorry, hatte deine Antwort völlig übersehen.
Ich muss dann also vesuchen, neben meinem neuem ClassLoader zum Laden der geänderten Klassen also auch die Standard Java-Klassen mitzuladen?
Einen try-catch-Block habe ich natürlich um meine Methode drumherum gebastelt.
Ich werde mit deiner Hilfe erstmal weiter versuchen, dass Problem zu lösen.


--- alte Antwort ---

Hallo KSG9|sebastian,

es wäre sehr nett, wenn du für dein Problem einen neuen Topic postest.
Sonst beziehen sich die Antworten der anderen auf deine Probleme, obwohl ich diesen Topic iniziiert habe.

@ Mag1c, Bleiglanz:

Die gesamte Applikation, an der ich entwickle, läuft ganz grob wie folgt ab:

1) ich starte meine Applikation
2) wähle .java-Dateien von irgendwo auf der Festplatte oder auch CD-/DVD-Laufwerk aus
3) diese .java-Dateien müssen um zusätzliche Informationen erweitert werden, also verändert werden
    -> ich kopiere diese Dateien an eine bestimmte Stelle, um nicht in den originalen Dateien was zu ändern
4) von diesen kopierten Dateien brauche ich das class-Objekt, um mittels Reflection an die Klassen, Methoden etc. dieser Dateien zu kommen

Punkt 4 war bisher meine Problemstelle, ich hatte es bisher nicht geschafft, an die class-Objekte der kopierten Dateien zu kommen, weil diese ja zum Start der Applikation (und damit auch Start der JVM) nicht bekannt/geladen waren. 
Ich kann diese Klassen jetzt aber mittlerweile nachladen. Dazu kompiliere ich die geänderten Dateien und schreibe sie in ein Verzeichnis ausserhalb des Class-Path
Der Zugriff auf die geänderten Klassen erfolgt mit dieser Methode:


```
private Object neuesExemplar(String pfad, String klassename) throws Exception
{		
    URL url = new File(pfad).toURL();
    URLClassLoader cl = new URLClassLoader(new URL[]{url}, null);
    Class c = cl.loadClass(klassename);
    return c.newInstance();
}
```


Diese Methode läuft eigentlich prima, aber wenn es sich bei der nachzuladenen Klasse um eine abstrakte Klasse handelt oder um ein Interface, bekomme ich halt eine InstantiationException in der Zeile _c.newInstance()_.
Das ist für mich noch nallvollziehbar, weil man von abstrakten Klassen keine Instanzen erzeugen darf.
Aber teilweise erhalte ich die InstantiationException auch bei Klassen, die weder abstrakt noch ein Interface sind.

Daher meine Frage: Woran kann diese Exception liegen?? Eine Klasse, bei der ich diese Exception auch erhalten habe, hat folgende grobe Struktur (siehe auch meine Antwort zuvor):


```
public class A extends class1 implements interface1 
{ 
     class B extends class 2 {} 
} 
class C extends class3 implements interface3 {} 
class D extends class4 implements interface4 {}
```
Dabei sind class1, interface1 usw. irgendwelche Standard java-Klassen/Interfaces wie z.B. JFrame oder ActionListener

Ich hoffe ich habe mein Problem gut genug erklären können und ihr könnt mir weiterhelfen !!


Gruß Simon


----------



## Bleiglanz (19. Okt 2005)

pack alles in ein try-catch und gib in der Methode null zurück, wenn das newInstance scheitern sollte

>>Woran kann diese Exception liegen

welche Exception fliegt denn? Stacktrace usw.??

wie gesagt, das könnte am fehlenden "Parent" liegen


bist du sicher dass du das wirklich auf ByteCode ebene machen musst? vielleicht reicht ja schon ein dynamic Proxy oder sowas?


----------



## Mag1c (19. Okt 2005)

Hi,

du solltest das ganze ein wenig umbauen, so daß du nur genau einen eigenen ClassLoader benutzt. Es ist nicht so günstig, für jeden neuen Aufruf eine neue ClassLoader-Instanz zu erzeugen. Evtl. hängt auch dein Problem damit zusammen. Abhängige Klassen werden über den ClassLoader der Klasse geladen. Wird eine Klasse über zwei ClassLoader geladen, so sind die beiden (und auch davon gebildete Instanzen) inkompatibel. Das gilt auch, wenn die .class-Files identisch sind.

Sorry, wenn das ein wenig kompliziert klingt. Das Thema ist nicht einfach 

zu deinem Beispiel: Sind die Klassen public ? Haben die einen Default-Konstruktor der auch public ist ?
Und von einer inner-Class, die nicht statisch ist, kannst du auch keine Instanz außerhalb der umgebenden Klasse erzeugen.

Gruß
Mag1c


----------

