JAXB 在 Tomcat 9 和 Java 9/10 上不可用情况我尝试了什么分析溶液注意

2022-09-01 10:57:51

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

编辑:不,这不是如何解决java.lang.NoClassDefFoundError:Java 9中的javax/xml/bind/JAXBException的副本 - 正如你从我尝试过的部分可以看出的那样,我已经尝试了建议的解决方案。

情况

我们有一个在Tomcat上运行并依赖于JAXB的Web应用程序。在迁移到 Java 9 的过程中,我们选择添加 JAXB 引用实现作为常规依赖项

使用嵌入式Tomcat从IDE启动应用程序时,一切都有效,但是当在真正的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'sCLASSPATH

也许将 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

不:

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

除了不同的类和堆栈跟踪之外,这与之前发生的事情一致:该类被加载了两次,一次是从java.xml.bind(必须是系统类加载器)加载的,另一次是从JAR加载的(我假设由JAR的应用程序加载器)。JAXBContextImpl

搜索错误

搜索 Tomcat 的错误数据库我找到了#62559。这可能是同样的错误吗?

将 JAR 添加到 Tomcat 的lib

根据 Tomcat 用户邮件列表上给出的建议,我将 JAXB JAR 添加到 Tomcat 的目录中,但得到了与应用程序 lib 文件夹中相同的错误。CATALINA_BASE/lib


答案 1

分析

首先是一些随机事实:

  • 如果没有给出类装入器,则在查找 JAXB 实现时将使用线程的上下文类装入器 - 即使您调用也是如此(有人可能会错误地认为它使用提供的类实例的装入器)JAXBContext::newInstancenewInstance(Class...)
  • Tomcat 构建了一个小型类装入器层次结构,以将 Web 应用程序彼此分开
  • 由于不依赖于java.xml.bind模块,在Java 9中,JAXB类不是由引导程序或系统类装入器装入的

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

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

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

  • 我们添加了 JAXB 作为常规依赖项,因此它由 Web 应用程序的类加载器加载
  • 就像在Java 8上一样,JAXB搜索系统类加载器,但是,人们看不到应用程序的加载器(只是相反)
  • JAXB未能找到实现并陷入困境

溶液

解决方案是确保 JAXB 使用正确的类装入器。我们知道有三种方式:

  • 打电话,但这不是一个好主意Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
  • 创建一个上下文解析器,但这需要JAX-WS,感觉就像用另一个邪恶替换一个邪恶一样。
  • 使用包接受变体(Java EE 7中的Javadoc),该变体也采用类装入器并传递正确的装入器,尽管这需要一些重构JAXBContext::newInstance

我们使用第三个选项,并重构为 的包接受变体。卑微的工作,但解决了问题。JAXBContext::newInstance

注意

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


答案 2

请尝试以下操作及其依赖项。有关最新版本,请参阅 Maven 存储库

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

它还包含 Java Service Loader 描述符。请参阅在 Java 9+ 中使用 JAXB


推荐