JAXB 在 Tomcat 9 和 Java 9/10 上不可用

2024-02-18

TLDR:在 Java 9/10 上,Tomcat 中的 Web 应用程序无法访问 JAXB,即使其参考实现存在于类路径中。

Edit:不,这不是重复的如何解决 Java 9 中的 java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException https://stackoverflow.com/q/43574426/2525313- 正如你可以看出的我尝试过的部分,我已经尝试了建议的解决方案。

情况

我们有一个在 Tomcat 上运行并依赖于 JAXB 的 Web 应用程序。在迁移到 Java 9 的过程中,我们选择添加作为常规依赖项的 JAXB 参考实现 https://stackoverflow.com/a/48204154/2525313.

从 IDE 启动应用程序时一切正常带有嵌入式 Tomcat https://tomcat.apache.org/tomcat-9.0-doc/api/org/apache/catalina/startup/Tomcat.html,但是当在真实的 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 ...]

Note:

在模块路径或类路径中未找到 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

也许将 JAR 添加到 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

Nope:

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]

这显然是同一个类,所以显然它是由两个类加载器加载的。我猜测系统类加载器和应用程序的类加载器 https://tomcat.apache.org/tomcat-9.0-doc/class-loader-howto.html#Class_Loader_Definitions,但是为什么要加载JAXBContext被委托给系统类加载器一次但并不总是?看起来就像应用程序的类加载器的委托行为在程序运行时发生了变化。

添加模块

我真的不想添加java.xml.绑定,但我还是尝试了一下,将其添加到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.绑定(必须是系统类加载器)和另一次(我假设是来自 JAR 的应用程序加载器)。

寻找错误

搜索 Tomcat 的 bug 数据库 https://bz.apache.org/bugzilla/query.cgi我发现#62559 https://bz.apache.org/bugzilla/show_bug.cgi?id=62559。这可能是同样的错误吗?

将 JAR 添加到 Tomcat 中lib

下列的Tomcat 用户邮件列表上给出的建议 http://mail-archives.apache.org/mod_mbox/tomcat-users/201807.mbox/%3cb1ca948e-f8d3-5c5e-9aaf-0c8c6c9f84d0@christopherschultz.net%3e,我将 JAXB JAR 添加到 Tomcat 的CATALINA_BASE/lib目录,但出现与应用程序的 lib 文件夹中相同的错误。


Analysis

首先一些随机事实:

  • if not 给定一个类加载器 https://javaee.github.io/jaxb-v2/doc/user-guide/ch06.html#d0e6919, JAXBContext::newInstance将使用线程的上下文类加载器 https://docs.oracle.com/javase/10/docs/api/java/lang/Thread.html#getContextClassLoader()当寻找 JAXB 实现时 - 即使您调用也是如此newInstance(Class...)(人们可能会错误地认为它使用提供的类实例的加载器)
  • Tomcat 构建一个小的类加载器层次结构 https://tomcat.apache.org/tomcat-9.0-doc/class-loader-howto.html将 Web 应用程序彼此分开
  • 不依赖模块java.xml.绑定,在Java 9中,JAXB类不是由引导程序或系统类加载器加载的

以下是 Java 8 上发生的事情:

  • 我们不将类加载器传递给 JAXB(哎呀),因此它使用线程的上下文类加载器
  • 我们的猜测是,Tomcat 没有显式设置上下文类加载器,因此最终会与加载 Tomcat 的类加载器相同:系统类加载器
  • 这太棒了,因为系统类加载器可以看到整个 JDK,因此 JAXB 实现也包含在其中

Java 9 进入 - 钢琴停止演奏,每个人都放下苏格兰威士忌:

  • 我们添加了JAXB 作为常规依赖项 https://stackoverflow.com/a/48204154/2525313所以它是由网络应用程序的类加载器加载的
  • 就像在 Java 8 上一样,JAXB 搜索系统类加载器,但无法看到应用程序的加载器(只有相反)
  • JAXB 无法找到实现并崩溃

Solution

解决方案是确保 JAXB 使用正确的类加载器。我们知道三种方法:

  • call Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());但这并不是一个好主意
  • create 上下文解析器 https://docs.oracle.com/javaee/7/api/javax/ws/rs/ext/ContextResolver.html,但这需要 JAX-WS,感觉就像用一种邪恶取代另一种邪恶
  • 使用包接受变体JAXBContext::newInstance (Java EE 7 中的 Javadoc https://docs.oracle.com/javaee/7/api/javax/xml/bind/JAXBContext.html#newInstance-java.lang.String-java.lang.ClassLoader-)也需要一个类加载器并传递正确的加载器,尽管这需要一些重构

我们使用第三个选项并针对接受包的变体进行了重构JAXBContext::newInstance。虽然是琐碎的工作,但解决了问题。

Note

User curlals https://stackoverflow.com/users/10141364/curlals提供了关键信息,但删除了他们的答案。我希望这不是因为我要求进行一些编辑。所有的功劳/业力都应该归于他们! @curlals:如果您恢复并编辑您的答案,我将接受并投票。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JAXB 在 Tomcat 9 和 Java 9/10 上不可用 的相关文章

随机推荐