2 votes

AbstractMethodError avec la compilation dynamique de java et le chargement de classe avec réflexion

J'essaie de faire ce qui suit en ce qui concerne Refleciton, dites-moi si c'est possible.

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

}

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

J'ai regroupé les classes ci-dessus dans un jar et je l'ai fait tourner sur une JVM. Maintenant, j'essaie de créer une autre classe au moment de l'exécution, de la compiler au moment de l'exécution et d'essayer d'utiliser la réflexion pour invoquer la fonction execute() de la classe B.

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

Code de réflexion :

Classloader = urls des jars de l'application et url de C.class, compilés au moment de l'exécution. Chargeur parent - Thread.currentThread().getContextClassLoader() Je configure également le chargeur de classe du contexte du thread actuel avec le chargeur de classe créé ci-dessus.

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]);   

Je suis capable d'obtenir la méthode et l'invoke est également appelé. Cependant, lorsque l'exécution de la classe B est appelée, elle tente d'appeler la méthode doWork() et je me heurte alors à une AbstractMethodError. En faisant des recherches sur l'exception, je vois que l'exception se produit avec des classloaders/jars incorrects. Mais je ne suis pas sûr de la manière dont je dois m'y prendre pour la corriger dans mon cas.

Quelqu'un peut-il m'aider ?

0voto

Holger Points 13789

Tout d'abord, clarifions les mythes concernant le chargeur de classe de contexte d'une Thread à moins que le code ne demande explicitement ce chargeur par l'intermédiaire de Thread.getContextClassLoader() (et l'utilise), il n'a aucune pertinence pour le chargement des classes.

Chaque classe a un chargeur de classe initiateur qui a défini la classe et les références aux autres classes de cette classe sont toujours résolues via ce chargeur de classe initiateur. Ceci s'applique même au chargement réflectif via Class.forName(String) (sans un ClassLoader ) ; il utilisera le chargeur de classe initiateur de l'appelant.

Si vous voulez charger C via un chargeur de classes personnalisé qui doit avoir accès à B parce que C le sous-classe, la meilleure façon de déterminer le chargeur parent pour la création de votre nouveau chargeur est la suivante B.class.getClassLoader() . Cependant, dans votre configuration simple, c'est le même chargeur que celui retourné par ClassLoader.getSystemClassLoader() qui est aussi le chargeur de classe par défaut tel que retourné par Thread.getContextClassLoader() c'est pour ça que ça a marché.

Vous savez que ça a marché parce que vous avez pu charger C avec succès. Alors que les autres références peuvent être résolues paresseusement, la superclasse directe doit être résolue immédiatement, donc B est dans le champ d'application de C Le chargeur de l'entreprise.

En supposant que l'absence d'une déclaration de execute() en A ou un abstract modificateur sur class B sont juste des oublis faits en postant la question, la raison pour laquelle l'invocation de la méthode ne fonctionne pas est beaucoup plus simple : la méthode abstract void doWork(); n'est pas public .

Depuis B y C sont chargés par des chargeurs de classes différents, ils sont considérés comme résidant dans des paquets différents, indépendamment de leur nom qualifié. Par conséquent, la méthode doWork() en C ne surcharge pas la méthode doWork() en B . Cela n'a pas été détecté au moment de la compilation, car, au moment de la compilation, il n'y a pas de hiérarchie de chargeurs de classes. Le compilateur considère donc B y C pour résider dans le même package, sur la base de leur nom qualifié (ou de la déclaration explicite du package). Par conséquent, le compilateur suppose que C.doWork() met en œuvre B.doWork() y C peut être déclaré non abstract .

Si vous déclarez la méthode doWork() en B como public cela devrait fonctionner. Et cela devrait fonctionner beaucoup plus simplement :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X