web-dev-qa-db-fra.com

JAXB non disponible sur Tomcat 9 et Java 9/10

[~ # ~] tldr [~ # ~] : On Java 9/10, une application web dans Tomcat n'a pas accès à JAXB même si son implémentation de référence est présente sur le chemin de classe.

Edit : Non, ce n'est pas un doublon de Comment résoudre Java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException in = Java 9 - comme vous pouvez le voir dans la section Ce que j'ai essayé, j'ai déjà essayé les solutions proposées.

La situation

Nous avons une application Web qui fonctionne sur Tomcat et dépend de JAXB. Lors de notre migration vers Java 9 nous avons opté pour l'ajout de l'implémentation de référence JAXB en tant que dépendance régulière .

Tout a fonctionné lors du lancement de l'application à partir du IDE avec Tomcat intégré , mais lors de son exécution sur une véritable instance de Tomcat, j'obtiens cette erreur:

Caused by: Java.lang.RuntimeException: javax.xml.bind.JAXBException:
    Implementation of JAXB-API has not been found on module path or classpath.
 - with linked exception:
[Java.lang.ClassNotFoundException: com.Sun.xml.internal.bind.v2.ContextFactory]
    at [... our-code ...]
Caused by: javax.xml.bind.JAXBException: Implementation of JAXB-API has not been found on module path or classpath.
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.Java:278) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.ContextFinder.find(ContextFinder.Java:421) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.Java:721) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.Java:662) ~[jaxb-api-2.3.0.jar:2.3.0]
    at [... our-code ...]
Caused by: Java.lang.ClassNotFoundException: com.Sun.xml.internal.bind.v2.ContextFactory
    at jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.Java:582) ~[?:?]
    at jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.Java:190) ~[?:?]
    at Java.lang.ClassLoader.loadClass(ClassLoader.Java:499) ~[?:?]
    at javax.xml.bind.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.Java:122) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.Java:155) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.Java:276) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.ContextFinder.find(ContextFinder.Java:421) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.Java:721) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.Java:662) ~[jaxb-api-2.3.0.jar:2.3.0]
    at [... our-code ...]

Remarque:

L'implémentation de JAXB-API est introuvable sur le chemin du module ou le chemin de classe.

Ce sont les fichiers pertinents dans webapps/$app/WEB-INF/lib:

jaxb-api-2.3.0.jar
jaxb-core-2.3.0.jar
jaxb-impl-2.3.0.jar

Qu'est-ce qui se passe ici?

Ce que j'ai essayé

Ajout de fichiers JAR à CLASSPATH de Tomca

Il est peut-être utile d'ajouter les fichiers JAR au chemin d'accès aux classes de Tomcat dans setenv.sh?

CLASSPATH=
    .../webapps/$app/WEB-INF/lib/jaxb-api-2.3.0.jar:
    .../webapps/$app/WEB-INF/lib/jaxb-impl-2.3.0.jar:
    .../webapps/$app/WEB-INF/lib/jaxb-core-2.3.0.jar:
    .../webapps/$app/WEB-INF/lib/javax.activation-1.2.0.jar

Nan:

Caused by: javax.xml.bind.JAXBException: ClassCastException: attempting to cast
jar:file:.../webapps/$app/WEB-INF/lib/jaxb-api-2.3.0.jar!/javax/xml/bind/JAXBContext.class to
jar:file:.../webapps/$app/WEB-INF/lib/jaxb-api-2.3.0.jar!/javax/xml/bind/JAXBContext.class.
Please make sure that you are specifying the proper ClassLoader.    
    at javax.xml.bind.ContextFinder.handleClassCastException(ContextFinder.Java:157) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.Java:300) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.Java:286) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.ContextFinder.find(ContextFinder.Java:409) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.Java:721) ~[jaxb-api-2.3.0.jar:2.3.0]
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.Java:662) ~[jaxb-api-2.3.0.jar:2.3.0]
    at de.disy.gis.webmapserver.factory.DefaultWmsRequestFactory.initializeCommandExtractor(DefaultWmsRequestFactory.Java:103) ~[cadenza-gis-webmapserver-7.7-SNAPSHOT.jar:7.6]
    at de.disy.gis.webmapserver.factory.DefaultWmsRequestFactory.lambda$new$0(DefaultWmsRequestFactory.Java:87) ~[cadenza-gis-webmapserver-7.7-SNAPSHOT.jar:7.6]

C'est clairement la même classe, donc apparemment il a été chargé par deux chargeurs de classe. Je soupçonne le chargeur de classe système et le chargeur de classe de l'application , mais pourquoi le chargement de JAXBContext serait-il délégué au chargeur de classe système une fois mais pas toujours? Il semble que le comportement de délégation du chargeur de classe de l'application change pendant l'exécution du programme.

Ajout du module

Je ne veux pas vraiment ajouter Java.xml.bind, mais je l'ai quand même essayé en ajoutant ceci à catalina.sh:

JDK_Java_OPTIONS="$JDK_Java_OPTIONS --add-modules=Java.xml.bind"

Ne fonctionne pas non plus, cependant:

Caused by: Java.lang.ClassCastException:
Java.xml.bind/com.Sun.xml.internal.bind.v2.runtime.JAXBContextImpl
cannot be cast to com.Sun.xml.bind.v2.runtime.JAXBContextImpl
    at [... our-code ...]

Mis à part les différentes classes et traces de pile, cela correspond à ce qui s'est produit précédemment: la classe JAXBContextImpl a été chargée deux fois, une fois depuis Java.xml.bind (doit avoir été le chargeur de classe système) et une autre fois (je suppose par le chargeur de l'application du JAR).

Recherche de bugs

Recherche dans la base de données de bogues de Tomcat J'ai trouvé # 62559 . Serait-ce la même erreur?

Ajouter des fichiers JAR à Tomcat lib

Après conseils donnés sur la liste de diffusion des utilisateurs de Tomcat , j'ai ajouté les JAR JAXB à Tomcat's CATALINA_BASE/lib, mais a la même erreur que dans le dossier lib de l'application.

18
Nicolai

Une analyse

D'abord quelques faits aléatoires:

  • sinon avec un chargeur de classe , JAXBContext::newInstance utilisera le chargeur de classe de contexte du thread lors de la recherche de l'implémentation JAXB - c'est le cas même si vous appelez newInstance(Class...) (on pourrait penser à tort qu'il utilise le chargeur des instances de classe fournies)
  • Tomcat construit ne petite hiérarchie de chargeurs de classes pour séparer les applications Web les unes des autres
  • en ne s'appuyant pas sur le module Java.xml.bind , dans Java 9, les classes JAXB ne sont pas chargées par le bootstrap ou chargeur de classe système

Voici donc ce qui s'est passé sur Java 8:

  • nous ne transmettons pas de chargeur de classe à JAXB (oups), il utilise donc le chargeur de classe de contexte du thread
  • notre conjecture est que Tomcat ne définit pas explicitement le chargeur de classe de contexte et qu'il finira par être le même que celui chargé Tomcat: le chargeur de classe système
  • c'est dandy parce que le chargeur de classe système voit l'intégralité du JDK et donc l'implémentation JAXB qui y est incluse

Java 9 entre - le piano s'arrête de jouer et tout le monde baisse son scotch:

  • nous avons ajouté JAXB en tant que dépendance régulière et donc il est chargé par le chargeur de classe de l'application Web
  • tout comme sur Java 8, JAXB recherche le chargeur de classe système, cependant, et que l'on ne peut pas voir le chargeur de l'application (seulement l'inverse))
  • JAXB ne parvient pas à trouver l'implémentation et monte le ventre

Solution

La solution consiste à s'assurer que JAXB utilise le bon chargeur de classe. Nous connaissons trois façons:

  • appeler Thread.getCurrentThread().setContextClassLoader(this.getClass().getClassLoader()); mais ce n'est pas vraiment une bonne idée
  • create n résolveur de contexte , mais cela nécessite JAX-WS et cela ressemble à remplacer un mal par un autre
  • utilisez la variante acceptant les packages de JAXBContext::newInstance ( Javadoc from Java EE 7 ) qui prend également un chargeur de classe et passe le chargeur correct, bien que cela nécessite une refactorisation

Nous avons utilisé la troisième option et refactorisé vers la variante acceptant les packages de JAXBContext::newInstance. Travail subalterne, mais a résolu le problème.

Remarque

L'utilisateur curlals a fourni l'information essentielle, mais a supprimé sa réponse. J'espère que ce n'est pas parce que j'ai demandé quelques modifications. Tout crédit/karma devrait leur revenir! @curlals: Si vous restaurez et modifiez votre réponse, je l'accepterai et la voterai davantage.

9
Nicolai

Essayez ce qui suit et ses dépendances. Voir un référentiel Maven pour la dernière version .

<dependency>
  <groupId>org.glassfish.jaxb</groupId>
  <artifactId>jaxb-runtime</artifactId>
  <version>2.3.0.1</version>
</dependency>

Il contient également les descripteurs Java Service Loader. Voir tilisation de JAXB dans Java 9 +

6
Philippe Marschall