Webstart .class Datei über Reflection laden

kirax

Bekanntes Mitglied
Hi

ich habe ein Problem in einer Anwendung, die ich über Web Start deployen will.
Die Anwendung stellt eine API zur Verfügung, die es ermöglicht, .class Dateien zur Runtime nachzuladen, diese zu instantiieren und in der Anwendung zur Verfügung zu stellen.

Ohne zu sehr ins Detail zu gehen:
Das Hauptfenster stellt eine JComboBox dar, in der Objekte einer gemeinsamen Oberklasse angezeigt werden. Diese sollen mithilfe der Reflection-API zur Laufzeit aus gegebenen .class Dateien kreiert werden.
Zum Laden verwende ich diesen FileClassLoader:
Java:
package com.plus.jdkp.util.reflect;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class FileClassLoader extends ClassLoader {

    private static FileClassLoader instance = null;
    
    public static FileClassLoader getInstance() {
	if (instance == null) {
	    instance = new FileClassLoader();
	}
	return instance;
    }
    
    private FileClassLoader() {
    }

    public Class<?> loadFromFile(File file) throws IOException {
	if (file.length() == 0 || file.length() > Integer.MAX_VALUE) {
	    throw new IllegalArgumentException();
	}
	int length = (int) file.length();
	byte[] buf = new byte[length];
	FileInputStream fis = new FileInputStream(file);
	fis.read(buf);
	fis.close();
	return super.defineClass(null, buf, 0, length);
    }
}

Die ganze Anwendung kommt in einem Jar File (das natürlich signiert ist - mit dem gleichen Zertifikat wie die anderen verwendeten Jars). Sowohl wenn ich aus meiner IDE starte, als auch aus dem Jar, funktioniert es perfekt. Die .class Datei wird gelesen und instantiiert.
Starte ich das ganze allerdings über Java Web Start bekomme ich folgende Fehlermeldung ("Anwendung kann nicht gestartet werden."):

Code:
java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at com.sun.javaws.Launcher.executeApplication(Unknown Source)
	at com.sun.javaws.Launcher.executeMainClass(Unknown Source)
	at com.sun.javaws.Launcher.doLaunchApp(Unknown Source)
	at com.sun.javaws.Launcher.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.NoClassDefFoundError: com/plus/jdkp/output/GameInterface
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClassCond(Unknown Source)
	at java.lang.ClassLoader.defineClass(Unknown Source)
	at java.lang.ClassLoader.defineClass(Unknown Source)
	at com.plus.jdkp.util.reflect.FileClassLoader.loadFromFile(Unknown Source)
	at com.plus.jdkp.util.reflect.Reflection.getClassFromFile(Unknown Source)
	at com.plus.jdkp.gui.Gui.<init>(Unknown Source)
	at com.plus.jdkp.Main.main(Unknown Source)
	... 9 more
Caused by: java.lang.ClassNotFoundException: com.plus.jdkp.output.GameInterface
	at java.lang.ClassLoader.findClass(Unknown Source)
	at java.lang.ClassLoader.loadClass(Unknown Source)
	at java.lang.ClassLoader.loadClass(Unknown Source)
	... 17 more
Dabei liegt GameInterface innerhalb des Jarfiles im Verzeichnis com/plus/jdkp/output (wie in der Exc. angegeben).

Was habe ich übersehen? Oder funktioniert das dynamische Laden von classes überhaupt mit Java Web Start?

Viele Grüße
kirax
 
Dabei liegt GameInterface innerhalb des Jarfiles im Verzeichnis com/plus/jdkp/output (wie in der Exc. angegeben).
Das Jarfile ist eine Datei, der Inhalt der Jarfiles ist keine Datei, da nützt ein FileInputStream also wenig. Warum nimmst du nicht den URLClassloader?
 
Ich will ja nicht die GameInterface-Klasse mit meinem FileClassLoader laden. Den FileClassLoader habe ich, um die .class Dateien aus dem Dateisystem zu laden.
Das Problem ist, dass ich zur Laufzeit nicht weiß, wie die Klasse in der .class Datei die ich laden möchte, (fully qualified) heißt. Deshalb mache ich auch den Umweg über defineClass.
GameInterface liegt ja eigentlich im Classpath (im JAR, das über Webstart geholt wird).

Vielleicht sollte ich dazusagen: GameInterface ist diejenige Superklasse, deren Subklassen ich aus dem Dateisystem laden möchte.
 
Habe es gelöst.

Das Problem war, dass Webstart einen eigenen Classloader benutzt und deshalb laut Webstart-FAQ der System-Classloader keine Klassen findet.

Da GameInterface Oberklasse meiner zu ladenden Klasse ist, obliegt es meinem FileClassLoader, diese Klasse zu laden.

[JAVA=18]private FileClassLoader() {
}[/code]

Hier wird implizit ClassLoader() aufgerufen, der dann den ClassLoader.getSystemClassLoader() als parent verwendet.
Wie Wildcard schon angemerkt hat, findet mein FileClassLoader() die Klasse GameInterface nicht (da sie im jar liegt) und gibt deshalb die Zuständigkeit an seinen parent - den SystemClassLoader weiter. Der führt in Webstart jedoch zu nichts (s.o.) - deshalb wird die Klasse nicht gefunden.
Glücklicherweise liefert die Webstart-FAQ eine Problemlösung:
Einfach statt dem SystemClassLoader den Webstart-ClassLoader per Thread.getCurrent().getContextClassLoader() verwenden und diesen dann logischerweise als parent einsetzen.

[JAVA=18]private FileClassLoader() {
super(Thread.getCurrent().getContextClassLoader());
}[/code]

Das hat das Problem gelöst.

Btw: Wird die Anwendung nicht per Webstart gestartet, sind die beiden Classloader (System-Classloader und Thread.getCurrent().getContextClassLoader()) identisch.
 

Neue Themen


Zurück
Oben