ClassLoader + ClassCastException

Cybernd

Mitglied
Hi

Ich bezweifle zwar das jemand eine Antwort auf die Frage weiß, aber dennoch wärs wirklich Nett wenn jemand eine "Idee" hätte ;o)

Gegeben sei:
MyClassLoader extends ClassLoader {}

In Ihm überschreibe ich
#loadClass(String name, boolean resolve)

Um die Reihenfolge des Ladens zu beeinflußen. Grob gesagt will ich damit erwirken das zuerst die Klasse dieses ClassLoaders geladen wird obwohl vielleicht die selbe Klasse im Parent existiert. (Eigentlich eine belanglose Änderung, da ich den selben Effekt erreichen kann, indem ich dem Parent die entsprechende .class wegnehme)

Sowie:
protected Class<?> findClass(String name) throws ClassNotFoundException

In Ihr lade ich einfach meine Klasse per FileInputStream. Das daraus resultierende ByteArray wird per defineClass in die Class gewandelt und so zurückgegeben.

Nun tritt leider bei der Benutzung folgender mir unerklärlicher Effekt auf:

Object o = cl.loadClass(className).newInstance();
System.out.println("\t object: " + o);

=> Die #toString() des eigenen nachgeladenen ByteCodes wird zuverläßig ausgeführt.

Nun erweitere ich das Beispiel um folgende Zeile:
MyClass m = (MyClass) o;

Und erhalte somit eine
"ClassCastException: full.qualified.MyClass"


Mit Debuggen / Google komme ich leider auf keinen Grünen Zweig. Wie soll ich etwas Debuggen wovon ich nur weiß das beim Cast die Exception geworfen wird? Das "Warum wird sie geworfen?" würde mich ja interessieren.

Wenn ich das getName() auf o anwende erhalte ich den korrekten full qualified Classname. Dieser ist ident mit dem der eigentlichen MyClass.

Ich weiß lediglich das wohl auch andere dieses Problem kennen:
https://lists.xcf.berkeley.edu/lists/advanced-java/2002-May/018843.html

Leider existieren auf derartige Fragen keine Antworten.

Achja eventuell als Addon das Verhalten des eigenen Classloaders:
cl.loadClass(className) führt zu:
MyClass im cl laden
Object im cl laden => kann er nicht finden
Object im Parent des cl laden. Dieser entspricht dem SystemClassloader.

thx für Ideen / Hinweise
cybi
 
Mhmm

Folgende Erkentniss: Das zur compiletime bekannte Interface liegt im ApplikationsClassloader und unterscheidet sich somit vom Interface im eigenen ClassLoader. Sind zwar eigentlich zweimal die selben Interfaces aber dennoch halt im falschen Classloader.

Resultat: Exception.

Lösung des Problems: Anstelle des Interfaces über Reflection / Method.invoke() arbeiten.

cybi
 
Hallo!

Ich hab mal versucht dein Problem nachzubauen:

Mein interface: ITest

Code:
public interface ITest {
	public void bla();
}

meine implementierende Klasse: TestImpl

Code:
public class TestImpl implements ITest {
	public void bla() {
		System.out.println("BlaBlaBla");
	}
}

Mein ClassLoader MyClassLoader:

Code:
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MyClassLoader extends ClassLoader {

	public MyClassLoader() {
		//Parent null gesetzt.
		super(null);
	}

	public Class loadClass(String name) throws ClassNotFoundException {

		//Ich lade zur einfachheit nur Classen aus dem selben Verzeichnis 
		File f = new File("./" + name + ".class");

		//Nur TestImpl will ich mit meinem ClassLoader laden
		//alles andere deligiere ich an den normalen AppClassLoader
		if (!name.equals("TestImpl")) {
			ClassLoader cl = MyClassLoader.class.getClassLoader();
			System.out.println(cl + "->loadClass(" + name + ")");
			return MyClassLoader.class.getClassLoader().loadClass(name);
		}

		System.out.println("MyClassLoader->loadClass(" + name + ")");

		int len = (int) f.length();

		byte[] buf = new byte[len];

		try {
			FileInputStream fis = new FileInputStream(f);
			DataInputStream dis = new DataInputStream(fis);
			dis.readFully(buf);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return defineClass(buf, 0, buf.length);
	}
}

Meine Klasse Tester mit der ich den ganzen Schmus ausprobiere:

Code:
public class Tester {
	public static void main(String[] args) {

		Class clazz = null;

		MyClassLoader mcl = new MyClassLoader();

		try {
			clazz = mcl.loadClass("TestImpl");
			TestImpl test = (TestImpl) clazz.newInstance();
			test.bla();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Ausgabe:

Code:
MyClassLoader->loadClass(TestImpl)
java.lang.ClassCastException
sun.misc.Launcher$AppClassLoader@1ff5ea7->loadClass(java.lang.Object)
sun.misc.Launcher$AppClassLoader@1ff5ea7->loadClass(ITest)
	at Tester.main(Tester.java:10)

Das ist doch dein Problem oder?

Wenn ich allerdings auf das Interface Caste, dass mit dem "Normalen"
AppClassLoader gelanden wurde läufts einwandfrei durch.

Code:
public class Tester {
	public static void main(String[] args) {

		Class clazz = null;

		MyClassLoader mcl = new MyClassLoader();

		try {
			clazz = mcl.loadClass("TestImpl");
			ITest test = (ITest) clazz.newInstance();
			test.bla();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Ausgabe:

Code:
MyClassLoader->loadClass(TestImpl)
sun.misc.Launcher$AppClassLoader@1ff5ea7->loadClass(java.lang.Object)
sun.misc.Launcher$AppClassLoader@1ff5ea7->loadClass(ITest)
sun.misc.Launcher$AppClassLoader@1ff5ea7->loadClass(java.lang.System)
sun.misc.Launcher$AppClassLoader@1ff5ea7->loadClass(java.io.PrintStream)
BlaBlaBla

... hilft dir wahrscheinlich nicht, aber ich hab dann immerhin dein Problem
verstanden... ist ja auch mal was ;-)

Gruß Tom
 
hmm doch eventuell hilft es mir

Maybe ist das mein Denkfehler. Ich lade auch das Markerinterface mit dem eigenen erweiterten Classloader. Das könnte ich aber eigentlich ja genaugenommen aus dem Appclassloader beziehen. Hmm *probier*

Mhmm ist ja interessant. In der Tat es klappt.

Bisher probierte ich mich mit
MyLoader
=> loads Klasse
=> loads MarkerInterface
Apploader
=> loads Object

Wenn ich nun aber das MarkerInterface bei der nachzuladenden Komponente lösche ergibt sich das Verhalten

MyLoader
=> loads Klasse
AppLoader
=> loads MarkerInterface
=> loads Object

Da ja in dem Falle das MarkerInterface aus dem Apploader kommt kann der Cast funktionieren. Erstaunlicherweise kommt der eigene Loader damit klar das er gar nicht selber für das MarkerInterface zuständig ist.

Bringt mich allerdings zu dem Problem das ich den eigenen Loader irgendwie mit Intelligenz ausstatten muß, der verhindert das dieses Interace vom eigenen Loader nachgeladen wird.

Im Regulärfall wird dies ja erwirkt, indem der extended Loader immer zuerst den Parent fragt. Ich will aber die eigenen Klassen bewußt überschreiben können und somit dem eigenen Classloader bewußt im Zuge eines Pluginmechanismus eine höhere Priorität zuweisen.

Vielleicht ist dies eine dumme Idee ;P Jetzt weiß ich zumindestens wieso alle ClassLoader so arbeiten, das sie immer zuerst die Ressourcen aus dem Parent zu beziehen versuchen.

cybi
 
Zurück