# Zur Laufzeit eine Klasse laden, die auf jar-File zugreift



## Litti (15. Mai 2008)

Hallo zusammen,

ich kämpfe seit gestern mit einem Problem, zu dem ich einfach keine Lösung finde. Ich muss zur Laufzeit meines Programmes Klassen nachladen - soweit ja noch kein Problem. Das entsteht erst, wenn diese Klasse dann auch noch auf Jar-Bibliotheken zugreifen will (Name dieser Jar ist bekannt).
Ich habe das Szenario einfach mal auf das Wichtigste gekürzt und getestet, hier meine Test-Klasse (alle Files liegen im gleichen directory):


```
try
{
   URL urls [] = new URL[2];
   urls[0] = new URL("file:./JarTest");
   urls[1] = new URL("file:./JarFile.jar");
   URLClassLoader cl = new URLClassLoader (urls);
            
   // PutString gibt einfach in der toString() ein "hiho" zurück
   Class oClass = cl.loadClass("PutString");
   Object o = (Object)oClass.newInstance();

   System.out.println(o);	

   System.out.println ("Success!");

   // ITest ist ein Interface mit der Methode setFields()
   // JarTest implementiert ITest, erstellt in setFields() eine Klasse PutString und
   // gibt in der toString die toString von PutString zurück (also "hiho")
   Class iTestClass = cl.loadClass("JarTest");
   ITest test = (ITest)iTestClass.newInstance();
	   
   System.out.println ("Success!");	
	   
   test.setFields();
   System.out.println(test);
}catch (Exception ex)
{
   System.out.println ("Failed.");
   ex.printStackTrace ();
}
```

Ausgabe beim Ablaufen der Klasse:


```
hiho
Success!
Success!
Exception in thread "main" java.lang.NoClassDefFoundError: PutString
        at JarTest.setFields(JarTest.java:6)
        at Test.main(Test.java:28)
```

Ich mache da wohl einen grundsätzlichen Fehler beim Umgang mit ClassLoadern. Wäre toll wenn mir wer weiterhelfen könnte, oder auch nur einen Anstoß, eine Idee...

LG Litti


----------



## thE_29 (15. Mai 2008)

Deine Klasse heißt PutString?

Class oClass = cl.loadClass("PutString")

hier muss der Klassennamen + package angegeben sein!


----------



## Litti (15. Mai 2008)

Hm ja hab das an der Stelle vlt etwas schlecht erklärt:

PutString ist Bestandteil der JarFile.jar. Diese jar übergebe ich zu Beginn dem ClassLoader. Dann hole ich mir PutString einmal direkt heraus, um zu sehen ob es zunächst einmal soweit funktioniert. Ist auch der Fall, da "Success" und "hiho" ausgegeben werden. Dann hole ich mir die Klasse JarTest, die ebenfalls PutString nutzt. Jetzt greife ich also indirekt auf PutString zu, eben über die Klasse JarTest: JarTest implementiert ein Interface, welches die Methode setFields() definiert. In dieser Methode wird dann ein Objekt von PutString erstellt --> wie man an der Ausgabe sieht, kommt der ClassLoader mit diesem indirekten Aufruf nicht mehr zu Recht, da ihm PutString nun plötzlich unbekannt ist...

Ich hoffe es ist ungefähr klar was ich vorhab...ich will Klassen zur Laufzeit laden, die eben auch sonst nur mit einem "javac -cp jarfile.jar Klasse.java" kompiliert werden könnten.


----------



## Litti (15. Mai 2008)

Hat keiner irgendwelche Ideen? Ich vermute mal es hat was mit diesem ganzen ClassLoader-Konzept von Java zu tun, bei dem ich scheinbar noch nicht ganz durchsteig...


----------



## Wildcard (15. Mai 2008)

Eine Klasse kann prinzipiell erstmal nur einmal geladen werden. Die einzige Möglichkeit sie wieder zu entladen, besteht darin, den ClassLoader wegzuwerfen. Du wirfst deinen Classloader wohl weg.


----------



## Guest (16. Mai 2008)

Danke für die Antwort, leider versteh ich sie nicht ganz...
Glaubst du es liegt daran, dass ich die Klasse PutString bereits einmal geladen habe, bevor JarTest es selbst tun müsste? Habe ich ausprobiert, in dem ich das erste Laden von PutString auskommentiert habe: kein Erfolg, selbe Fehlermeldung.
Ich will ja auch keine Klasse entladen, sondern einfach folgendes: Eine Klasse laden, die nur mit einer (ebenfalls zur Compilezeit unbekannten) jar-Bibliothek funktioniert. Also muss ich auch dieses jar-File nachladen (Name und Ort bekannt). Aber wie muss ich das nun mit den ClassLoadern regeln, damit auch erkannt wird, dass die Inhalte der jar ja längst geladen sind?


----------



## cburghardt (16. Mai 2008)

Noch komplizierter gehts's ja nicht, zum Kompilieren von JarTest brauchst du die PutString ja auch, somit verstehe ich den Sinn und Zweck von deinem dynamischen Laden nicht so richtig, wenn du eine Abhängigkeit zur Compile-Zeit hast.

Anyway, ich bin mir nicht ganz sicher, aber versuch mal:

```
Class iTestClass = cl.loadClass("JarTest", true);
```


----------



## Litti (16. Mai 2008)

Funktioniert leider auch...nach dem Umbau des Programms (Zugriff auf Methode ist ja protected) selbe Fehlermeldung.

Puh, bitte sagt mir dass es dazu irgendeine Lösung gibt. Das Jar-File ist ja vorhanden, irgendwas muss da doch zu machen sein...


----------



## thE_29 (16. Mai 2008)

Also eigentlich müsste das doch so gehen!

Hier ist mein Code zum Laden eines Jar Files


```
File fJar = new File(strJar);
    URL url = null;
    try
    {
      //holt sich das jar file in url form
      url = fJar.toURL();
      URLClassLoader urlcl = new URLClassLoader(new URL[]   {url});
      Class clazz = Class.forName(strPackage, true, urlcl);
      Constructor cons = null;
      cons = clazz.getConstructor(formparas); //holt den Konstruktor über Objekte
      currentProgram = cons.newInstance(actargs); //startet den Konstruktor und somit das Programm und merkt sich das Objekt
    catch (Exception ex)
{
ex.printStackTrace();
}
```

Die Frage ist halt, ob er die anderen Dateien die man dem URLClassLoader übergibt auch mit berücksichtigt oder nicht!

Könntest du eventuell deine Testsituation irgendwo bereitstellen?


----------



## cburghardt (16. Mai 2008)

Das Problem ist ja nicht die Instanziierung sondern die Verwendung von PutString in der Klasse JarTest

Könntest du mal die PutString hier posten, vielleicht gibts eine Abhängigkeit


----------



## Guest (16. Mai 2008)

Super dass ihr versucht mir zu helfen!

Hier die PutString, aber ich weiß nicht ob euch das wirklich hilft...


```
public class PutString {
	
	public String toString(){
		return "hiho";
	}
}
```

Also wirklich nur die toString() überschrieben. Diese Datei ist einziger Bestandteil der JarFile.jar. TestClass nutzt diese Bibliothek indem sie in der Methode setFields() (vorgegben durchs Interface ITest) ein PutString-Objekt erzeugt.


----------



## cburghardt (16. Mai 2008)

Mysteriös, dann kann es nichts mit dem Classloader zu tun haben, weil die Klasse ja instanziiert werden kann.
Gab es nach der Änderung beim loadClass(.., true) die gleiche Fehlermeldung? Nebenbei gesagt: der bessere Weg wäre übrigens
Class.forName(class, true, classloader)
Dann wird das Objekt nämlich auch initialisiert, was Classloader.loadClass NICHT macht.
NoClassDefFoundError heisst ja, dass zwar eine Klasse mit dem richtigen Namen gefunden wurde, aber die Interfaces zur Laufzeit nicht mehr die gleichen sind wie zur Compilezeit. Wenn also in deiner PutString Klasse nichts importiert wird, kann ich mir nur noch vorstellen dass noch eine andere Version dieser Klasse in deinem Classpath rumhängt.


...Carsten


----------



## Wildcard (16. Mai 2008)

Um mich zu wiederholen:
Ich behaupte du behälst die Referenz auf deinen URL Classloader nicht.
Sobald der GC ihn eingesammelt hat, ist auch die geladene Class futsch.


----------



## Litti (16. Mai 2008)

Hm, wann genau greift da der gc? Und was müsst ich deiner Meinung nach ändern?

Das mit dem NoClassDefFoundError ist ein guter Hinweis...hatte ich mir gar nicht so klar gemacht, dass das ja noch ein Unterschied zu dem Fall darstellt, dass gar keine Klasse gefunden wird. Aber durch den Classpath dürfte eigentlich nix schief gehen, den hab ger nicht gesetzt und bezieht sich dementsprechend nur aufs akutelle dir.

Ich hab jetzt mal den Code online gestellt, hab das Gefühl damit würd ichs auch euch etwas erleichtern:
test.tar.gz
Auch der Quellcode der PutString ist vorhanden, falls ihr noch einmal ein neues Jar erzeugen wollt.


----------



## Litti (21. Mai 2008)

Ich schieb das mal wieder hoch, ist leider für mich noch immer aktuell.

Und allmählich gibts auch nichts mehr, was ich ohne eine Lösung weitermachen könnte,,,*HELP*


----------



## thE_29 (21. Mai 2008)

Vielleicht solltest du eine Referenz auf den ClassLoader behalten und immer wieder bei new URLClassLoader den alten mitübergeben?!


----------

