JAXB недоступен на Tomcat 9 и Java 9/10 - программирование
Подтвердить что ты не робот

JAXB недоступен на Tomcat 9 и Java 9/10

TL;DR: на Java 9/10 веб-приложение в Tomcat не имеет доступа к JAXB, хотя его ссылочная реализация присутствует на пути к классу.

Edit: Нет, это не дубликат. Как разрешить java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException в Java 9 - как вы можете сказать по тому, что я пробовал, я уже пробовал предлагаемые решения.

Ситуация

У нас есть веб-приложение, которое работает на Tomcat и зависит от JAXB. Во время нашей миграции на Java 9 мы выбрали добавление эталонной реализации JAXB в качестве регулярной зависимости.

Все работало при запуске приложения из IDE со встроенным Tomcat, но при запуске его на реальном экземпляре Tomcat я получаю эту ошибку:

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 ...]

Замечания:

Внедрение JAXB-API не найдено на пути к модулю или пути к классам.

Это соответствующие файлы в webapps/$app/WEB-INF/lib:

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

Что здесь происходит?

Что я пробовал

Добавление JAR в Tomca CLASSPATH

Может быть, это помогает добавить setenv.sh в путь класса Tomcat в 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

Нету:

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]

Это явно тот же класс, поэтому, видимо, он был загружен двумя загрузчиками классов. Я подозреваю загрузчик системного класса и загрузчик классов приложений, но почему загрузка JAXBContext была делегирована загрузчику системного класса один раз, но не всегда? Похоже, что поведение делегации загрузчика класса приложения изменяется во время работы программы.

Добавление модуля

Я действительно не хочу добавлять java.xml.bind, но я все равно попробовал, добавив это в catalina.sh:

JDK_JAVA_OPTIONS="$JDK_JAVA_OPTIONS --add-modules=java.xml.bind"

Не работает, хотя:

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 ...]

Помимо разной трассы класса и стека, это соответствует тому, что было раньше: класс JAXBContextImpl загружался дважды, один раз из java.xml.bind (должен был быть загрузчиком системного класса) и еще один раз (я предполагаю, что загрузчик приложений из JAR).

Поиск ошибок

Поиск базы данных ошибок Tomcat я нашел # 62559. Может быть, такая же ошибка?

Добавление JAR в Tomcat lib

Следуя советам, приведенным в списке рассылки пользователей Tomcat, я добавил JAXB JAR в CATALINA_BASE/lib Tomcat CATALINA_BASE/lib, но получил ту же ошибку, что и в папке приложения lib.

4b9b3361

Ответ 1

Анализ

Сначала несколько случайных фактов:

Итак, что случилось на Java 8:

  • мы не передаем загрузчик классов в JAXB (упс), поэтому он использует загрузчик классов контекста потока
  • наша гипотеза состоит в том, что Tomcat не устанавливает явно загрузчик класса контекста, и в итоге он будет тем же, который загрузил Tomcat: загрузчик класса системы
  • этот денди, потому что загрузчик системного класса видит весь JDK и, следовательно, включенную в него реализацию JAXB

Входит Java 9 - пианино перестает играть, и каждый откладывает свой скотч:

  • мы добавили JAXB как обычную зависимость, и он загружается загрузчиком классов веб-приложения
  • Как и в Java 8, JAXB ищет загрузчик системного класса, и тот, кто не может видеть загрузчик приложения (только наоборот)
  • JAXB не может найти реализацию и идет в тупик

Решение

Решение состоит в том, чтобы убедиться, что JAXB использует правильный загрузчик классов. Мы знаем три способа:

  • вызовите Thread.getCurrentThread().setContextClassLoader(this.getClass().getClassLoader()); но это не очень хорошая идея
  • создать преобразователь контекста, но для этого требуется JAX-WS, и это похоже на замену одного зла другим
  • используйте принимающий пакет вариант JAXBContext::newInstance (Javadoc из Java EE 7), который также принимает загрузчик классов и передает правильный загрузчик, хотя это требует некоторого рефакторинга

Мы использовали третий вариант и переориентировались на пакетный вариант JAXBContext::newInstance. Черная работа, но исправила проблему.

Заметка

Пользовательские курьеры предоставили важную информацию, но удалили свой ответ. Я надеюсь, что это не потому, что я попросил несколько правок. Все кредиты/кармы должны идти к ним! @curlals: Если вы восстановите и отредактируете свой ответ, я приму и одобряю его.