2016-05-03 17 views
2

Ich versuche, die folgenden WRT refleciton, lassen Sie mich wissen, ob dies möglich istAbstractMethodError mit dynamischer Java-Kompilierung und das Laden von Klassen mit Reflexion

public class A { 
    void start(){ 
     execute(); 
    } 

} 

public class B extends A { 
    void execute() 
    { 
     doWork(); 
    } 
    public abstract void doWork(); 
} 

Ich habe die oben genannten Klassen in einem Glas verpackt und hat es auf einer Lauf JVM. Jetzt versuche ich eine andere Klasse zur Laufzeit zu erstellen, kompiliere sie zur Laufzeit und versuche mit Reflection die execute-Funktion von Klasse B aufzurufen().

public class C extends B { 
    @Override 
    public void doWork() 
    { 
     //Implementation 
    } 
} 

Reflection Code:

Classloader = Urls Anwendungs ​​Gläser und URL C.class, zur Laufzeit zusammengestellt. Übergeordneter Loader - Thread.currentThread(). GetContextClassLoader() Ich setze auch den Context Class Loader des aktuellen Threads auf den oben erstellten Classloader.

Class<? extends B> cls = (Class<? extends B>) Class.forName("className", true, clsLoader); 

Object obj = cls.newInstance(); 
Method method = obj.getClass().getSuperclass().getMethod("start", new Class[0]); 
method.invoke(obj, new Object[0]); 

Ich bin in der Lage, die Methode zu erhalten und ruft auch aufgerufen wird. Wenn jedoch die Ausführung der Klasse B aufgerufen wird, versucht sie, doWork() aufzurufen, und dann stosse ich auf einen AbstractMethodError. Wenn ich auf die Exception nachschaue, sehe ich, dass die Exception mit falschen Klassenlatern/Gläsern passiert. Aber ich bin mir nicht sicher, wie ich es in meinem Fall beheben kann.

Kann jemand helfen?

+0

ich nur raten, aber ich glaube, der Klassenlader von C versucht, die abstrakte Methode der Klasse B zu finden, aber es hat keine Sicht davon, daher die abstrakte Methode Fehler. Persönlich vermeide ich die Reflexion um jeden Preis und wenn ich Code dynamisch laden möchte, benutze ich OSGi. Ich vermeide es auch, Code dynamisch zu erstellen. Zu viele Gefahren, vor allem Sicherheit, und ich glaube, Sie können mit einem vollständigen Rattennest Code – Kerry

+0

@Kerry enden: der Klassenlader von 'C' hat keinen Grund, nach einer abstrakten Methode von' B' zu suchen. Es ist umgekehrt, wenn die abstrakte Methode von "B" aufgerufen wird, wird eine konkrete Implementierung in der Klassenhierarchie von "C" gesucht. Aber Sichtbarkeit ist in der Tat der Schlüssel hier. – Holger

Antwort

0

Zuerst klären wir die Mythen über die Kontextklasse Loader von Thread: Wenn der Code nicht explizit nach diesem Loader über Thread.getContextClassLoader() fragt (und verwendet), hat er überhaupt keine Relevanz für das Laden von Klassen.

Jede Klasse hat einen initiierenden Klassenlader, der die Klasse definiert, und Verweise auf andere Klassen innerhalb dieser Klasse werden immer über diesen initiierenden Klassenlader aufgelöst. Dies gilt sogar für die reflektive Belastung über Class.forName(String) (ohne explizite ClassLoader); Es wird den initiierenden Klassenlader des Aufrufers verwenden.

Wenn Sie über eine benutzerdefinierte Class Loader zu laden C, die den Zugang zu B haben muss, weil C Subklassen es, der beste Weg, um die Eltern-Loader für die Erstellung des neuen Laders zu bestimmen B.class.getClassLoader() ist. In Ihrem einfachen Setup ist es jedoch derselbe Loader, der von ClassLoader.getSystemClassLoader() zurückgegeben wird, der auch der Standardklasse-Lader ist, der von Thread.getContextClassLoader() zurückgegeben wird, deshalb hat es funktioniert.

Sie wissen, dass es funktionierte, weil Sie C erfolgreich laden konnten. Während andere Referenzen träge gelöst werden können, muss die direkte Superklasse sofort aufgelöst werden, so dass B im Bereich des Loaders von C liegt.

Unter der Annahme, dass das Fehlen einer Erklärung von execute() in A oder einen abstract Modifikator auf class B ist nur durch die Veröffentlichung der Frage gestellt Versäumnisse der Grund, warum die Methode Aufruf nicht funktioniert, ist viel einfacher: die Methode abstract void doWork(); ist nicht public .

Da B und C von verschiedenen Klassenladeprogrammen geladen werden, gelten sie unabhängig von ihrem qualifizierten Namen als in verschiedenen Paketen gespeichert. Daher überschreibt das Verfahren doWork() in C nicht die Methode doWork() in B.Dies wurde zur Kompilierungszeit nicht erkannt, da zur Kompilierungszeit keine Klassenladerhierarchie vorhanden ist. Daher betrachtet der Compiler B und C im selben Paket basierend auf ihrem qualifizierten Namen (oder der expliziten Paketdeklaration). Daher nimmt der Compiler an, dass C.doWork() implementiert B.doWork() und C kann nicht abstract deklariert werden.

Wenn Sie die Methode doWork() in B als public deklarieren, sollte es funktionieren. Und es sollte viel einfacher arbeiten:

try(URLClassLoader l = new URLClassLoader(new URL[]{/* url pointing to C */})) { 
    l.loadClass("C").asSubclass(B.class).newInstance().execute(); 
} 
+0

Danke für die Erklärung. doWork() ist in der Tat eine öffentliche Methode. Ich habe auch meine ursprüngliche Post mit diesen Informationen bearbeitet. – user3421442