Eclipse / OSGI, Java 11, JAXB et le chargeur de classe

Nov 23 2020

J'ai deux modules Java, A et B. XmlAnyElement (lax = true) et doit donc être ajouté au contexte JAXB.

Cela fonctionne bien en Java simple - le chargeur de classe de B voit toutes les classes pertinentes et peut instancier le contexte JAXB avec:

JAXBContext.newInstance(RootFromA.class, RootFromB.class)

Maintenant, j'essaye la même chose avec OSGI (B est un plug-in Eclipse et A est la bibliothèque principale qui sera également utilisée par un module de ligne de commande Java simple C). Après de nombreux essais et erreurs, j'ai réussi à amener A et B à voir à la fois l'API JAXB et l'implémentation via les importations de packages OSGI. Le problème est que l'appel de newInstance comme ci-dessus semble utiliser le classloader de l'API JAXB, pas celui de RootFromA, et certainement pas celui de RootFromB. En tant que tel, il ne parvient même pas à voir l'implémentation JAXB et se plaint de ne pas trouver la classe ContextFactory.

J'ai réussi à résoudre ce problème en appelant une version différente de newInstance:

JAXBContext.newInstance(
  RootFromA.class.getPackageName()
    + ":" + RootFromB.class.getPackageName(),
  RootFromB.class.getClassLoader())

Je n'aime pas ça, pour deux raisons:

  1. Mon code "client" (B étant le client de l'assistant JAXB dans A) doit fournir manuellement un chargeur de classe approprié.
  2. Je dois fournir des fichiers jaxb.index dans tous les packages référencés qui répertorient les classes de contexte, même si mon code en est parfaitement conscient et obtient en fait le nom du package des classes.

Il n'y a probablement aucun moyen de contourner (1), car seul B connaît l'ensemble complet des classes et peut décider dont le chargeur de classe est capable de toutes les voir. Je crains cependant que je puisse rencontrer plus de problèmes une fois que j'ajoute les modules d'extension C et D qui se connectent à B via les points d'extension Eclipse et fournissent des classes supplémentaires pour le contexte JAXB - le classloader de B pourra-t-il les voir?

Mais je trouverais vraiment un moyen de me débarrasser des fichiers d'index statiques requis pour (2). L'ensemble complet des classes de contexte est dynamique et décidé en Java simple par un ServiceLoader et dans Eclipse par un point d'extension. Dans les deux cas, j'ai un accès direct à l'ensemble complet des classes qui devraient appartenir au contexte, et j'envisage donc de devoir ajouter manuellement des fichiers jaxb.index à chaque package redondant et donc une source d'erreur potentielle et inutile.

Est-ce que je manque quelque chose? Existe-t-il une meilleure façon de faire cela? Et pourquoi n'y a-t-il pas une méthode newInstance qui prend un ensemble de classes de contexte et un chargeur de classe?

Réponses

MarianSchedenig Nov 26 2020 at 01:55

Il semble que j'ai trouvé une solution. Mon problème était de devoir gérer des chargeurs de classe à deux fins différentes:

  1. Le chargeur de classe utilisé par JAXB pour instancier le contexte
  2. Les chargeurs de classe de mes différentes classes de modèle

Comme décrit ci-dessus, (1) peut être spécifié en tant que paramètre dans JAXBContext.newInstance (), mais uniquement lors de la spécification du modèle en tant que noms de package au lieu de classes de modèle individuelles. Cela signifie que JAXB doit rechercher les classes par lui-même, et c'est le seul chargeur de classe qu'il peut utiliser pour le faire est (1) - qui ne peut pas voir toutes les classes de modèle si elles sont réparties sur plusieurs bundles.

Un autre thread ( Pourquoi JAXB ne trouve-t-il pas mon jaxb.index lors de l'exécution dans Apache Felix? ) M'a appris que JAXB utilise par défaut le chargeur de classe de contexte de thread pour localiser l'implémentation du contexte. Définir cela sur un classloader qui voit l'implémentation (par exemple celle de mon propre bundle) est suffisant pour que JAXB trouve sa propre implémentation, ce qui me laisse libre d'appeler ma version préférée de newInstance () avec un tableau de classes. Et comme ces classes ont déjà été chargées, JAXB peut les utiliser telles quelles et n'a pas à se soucier de leurs différents chargeurs de classes.

En bref:

Class[] myClasses = getModelClasses(); // gather all model classes, which may have different classloaders
Thread thread = Thread.currentThread();
ClassLoader originalClassLoader = thread.getContextClassLoader();
thread.setContextClassLoader(getClass().getClassLoader()); // my own bundle's classloader
JAXBContext context = JAXBContext.newInstance(myClasses);
thread.setContextClassLoader(originalClassLoader); // reset context classloader

Le contenu de la manipulation du chargeur de classe de contexte peut être encapsulé dans une fermeture automatique, ce qui me permet simplement d'encapsuler l'instanciation JAXBContext dans un bloc try-with-resources.