TLDR :在Java 9/10上,Tomcat中的Web应用程序无法访问JAXB,即使其引用实现位于类路径中。
编辑:不,这不是How to resolve java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException in Java 9的副本-正如您在我尝试过的内容部分所指出的那样,我已经尝试了建议的解决方案
我们有一个可在Tomcat上运行并依赖JAXB的Web应用程序。在迁移到Java 9的过程中,我们选择添加the JAXB reference implementation as a regular dependency。
从IDE with embedded 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
这是怎么回事?
CLASSPATH
也许可以将JAR添加到setenv.sh
中的Tomcat的类路径中?
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]
这显然是同一类,因此显然它已经由两个类加载器加载了。我怀疑the system class loader and the app's class loader,但是为什么将一次加载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)。
Searching Tomcat's bug database我发现了#62559。会是同样的错误吗?
lib
在advice given on the Tomcat user mailing list之后,我将JAXB JAR添加到Tomcat的CATALINA_BASE/lib
目录中,但是出现了与应用程序的lib文件夹相同的错误。
答案 0 :(得分:7)
首先是一些随机事实:
JAXBContext::newInstance
在寻找JAXB实现时将使用the thread's context class loader-即使您调用newInstance(Class...)
,情况也会如此(可能会错误地认为它使用了提供的类实例的加载程序)这就是Java 8的情况:
Java 9进入-钢琴停止弹奏,所有人放下苏格兰威士忌:
解决方案是确保JAXB使用正确的类加载器。我们知道三种方式:
Thread.getCurrentThread().setContextClassLoader(this.getClass().getClassLoader());
,但这并不是一个好主意JAXBContext::newInstance
(Javadoc from Java EE 7)的包接受变体,它也需要一个类加载器并传递正确的加载器,尽管这需要一些重构我们使用了第三个选项,并将其重构为JAXBContext::newInstance
的可接受包的变体。艰苦的工作,但解决了这个问题。
User curlals提供了关键信息,但删除了他们的答案。我希望不是因为我要求进行一些编辑。所有的信用/业力都应归功于他们! @curlals:如果您恢复并编辑答案,我会接受并支持。
答案 1 :(得分:5)
尝试以下操作及其依赖项。参见Maven repository for latest version。
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.0.1</version>
</dependency>
它还包含Java Service Loader描述符。参见Using JAXB in Java 9+
答案 2 :(得分:1)
TL; DR
一个对我有用的简单解决方案就是升级Hibernate版本。
我在5.2.10.Final
版本中使用了Hibernate,它们依赖于JAXB。但是,当我用Tomcat替换undertow时,该依赖性消失了。我找到了这个问题,但是没有一个答案能真正解决我的问题。当我发现jpa-model-gen
是我很快意识到的问题时,唯一依赖Hibernate的依赖就是寻找JAXB。将休眠版本更新到更高版本可以解决我的问题。
答案 3 :(得分:1)
我在使用CompletableFuture
的代码的特定部分中使用带有嵌入式Tomcat的Spring Boot(版本2.2.6)时遇到了这个问题。该代码与Java 8以及在Java 12中通过的相关单元测试完美配合。仅当使用Java 11或12在Tomcat中执行该应用程序时,该问题才会出现。
调试问题,我发现问题与以下事实有关:在ClassLoader
的{{1}}中使用了不同的CompletableFuture
。
Runner
第二个// here Thread.currentThread().getContextClassLoader().getClass()
// returns org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader
return CompletableFuture.runAsync(() -> {
// here returns jdk.internal.loader.ClassLoaders$AppClassLoader
});
无法加载JAXB类。此行为似乎仅在Java 9+中才存在,实际上是在Java 9 ClassLoader
返回带有主ForkJoinPool.common()
的{{1}}的{{1}}之前,但是在Java 9之后,它返回了系统Executor
的执行者。
由于ClassLoader
方法接受Thread
作为第二个参数,因此可以在代码中设置所需的ClassLoader
。这是一个可能的解决方案的示例。
首先,定义一个适当的CompletableFuture.runAsync()
:
Executor
然后使用该工厂将Executor
传递给ForkJoinWorkerThreadFactory
方法:
public class JaxbForkJoinWorkerThreadFactory implements ForkJoinWorkerThreadFactory {
private final ClassLoader classLoader;
public JaxbForkJoinWorkerThreadFactory() {
classLoader = Thread.currentThread().getContextClassLoader();
}
@Override
public final ForkJoinWorkerThread newThread(ForkJoinPool pool) {
ForkJoinWorkerThread thread = new JaxbForkJoinWorkerThread(pool);
thread.setContextClassLoader(classLoader);
return thread;
}
private static class JaxbForkJoinWorkerThread extends ForkJoinWorkerThread {
private JaxbForkJoinWorkerThread(ForkJoinPool pool) {
super(pool);
}
}
}
答案 4 :(得分:0)
我在使用JAXB时也遇到过类似的问题,即
未发现JAXB-API的实现
随机发生,很难复制。幸运的是,我发现了一个系统环境,上面的错误是连续的,而其他环境则运行正常。
通过对该问题的广泛研究,我发现了导致此问题的 classloader 问题。我进一步注意到,
JAXBContext对象是线程安全的(而marshaller / unmarshaller则不是),一旦启动,便可以重新使用。因此,