首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >JAXB在Tomcat 9和Java 9/10上不可用

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

Stack Overflow用户
提问于 2018-07-25 20:21:53
回答 5查看 13.3K关注 0票数 29

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

编辑:不,这不是How to resolve java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException in Java 9的复制品-正如你可以从我尝试的部分看出,我已经尝试了建议的解决方案。

情况

我们有一个运行在Tomcat上的web应用程序,它依赖于JAXB。在迁移到Java9的过程中,我们选择了添加the JAXB reference implementation as a regular dependency

当从集成开发环境with embedded Tomcat启动应用程序时,一切都正常工作,但是当在一个真实的Tomcat实例上运行它时,我得到了这个错误:

代码语言:javascript
复制
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中的相关文件

代码语言:javascript
复制
jaxb-api-2.3.0.jar
jaxb-core-2.3.0.jar
jaxb-impl-2.3.0.jar

这里发生什么事情?

我尝试过的

向Tomca的CLASSPATH添加JAR

也许将JAR添加到setenv.sh中Tomcat的类路径会有所帮助

代码语言:javascript
复制
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

不是:

代码语言:javascript
复制
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中添加了以下代码

代码语言:javascript
复制
JDK_JAVA_OPTIONS="$JDK_JAVA_OPTIONS --add-modules=java.xml.bind"

然而,也不能工作:

代码语言:javascript
复制
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加载的)。

搜索bug

Searching Tomcat's bug database我找到#62559了这会是同样的错误吗?

将JAR添加到Tomcat的lib

advice given on the Tomcat user mailing list之后,我将JAXB添加到Tomcat的CATALINA_BASE/lib目录中,但得到的错误与应用程序的lib文件夹中的错误相同。

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2018-08-07 17:34:18

分析

首先是一些随机的事实:

  • 如果不是given a class loaderJAXBContext::newInstance在查找JAXB实现时将使用the thread's context class loader -即使您调用newInstance(Class...) (可能会错误地认为它使用了提供的类实例的加载器)
  • Tomcat构建a small class loader hierarchy以将web应用程序彼此分开<代码>H29<代码>H110通过不依赖于模块java.xml.bind,在Java9中,JAXB类不会由引导程序或系统类加载器加载<代码>H211<代码>F212

下面是在Java 8上发生的事情:

Tomcat没有传递类加载器给JAXB (oops),所以它使用线程的上下文类加载器

  • 我们猜测Tomcat没有显式地设置上下文类加载器,所以它最终将是加载Tomcat的那个: system类

,因为系统类加载器可以看到整个

  • ,因此包含在其中的JAXB实现

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

  • 我们添加了JAXB as a regular dependency,所以它是由web应用程序的类加载器
  • 加载的,就像在Java8中一样,JAXB搜索系统类加载器,但是人们看不到应用程序的加载器(只能反过来)
  • JAXB找不到实现,并导致

崩溃

解决方案

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

  • 调用Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
  • create a context resolver,但这确实不是一个好主意,但这需要JAX-WS,感觉就像用另一个
  • 替换一个邪恶,使用JAXBContext::newInstance的包接受变体(<代码>C39),它也接受一个类加载器并传递正确的加载器,尽管这需要一些重构<代码>H240<代码>F241

我们使用了第三种选择,并对JAXBContext::newInstance的包接受变体进行了重构。卑微的工作,但解决了问题。

备注

User 提供了关键信息,但删除了他们的答案。我希望这不是因为我要求了一些编辑。所有的功劳/报应都应该归功于他们!@curlals:如果你恢复并编辑了你的答案,我将接受并提升它。

票数 16
EN

Stack Overflow用户

发布于 2018-07-25 22:13:42

尝试执行以下操作及其依赖项。请参阅Maven repository for latest version

代码语言:javascript
复制
<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+

票数 9
EN

Stack Overflow用户

发布于 2020-04-03 21:02:48

在使用Spring Boot (版本2.2.6)和嵌入Tomcat的代码中使用CompletableFuture的特定部分时,我遇到了这个问题。该代码与Java 8和Java 12中通过的相关单元测试完美配合。只有在Tomcat中使用Java 11或12执行应用程序时,才会出现该问题。

调试问题我发现这个问题与CompletableFutureRunner中使用了不同的ClassLoader有关。

代码语言:javascript
复制
// here Thread.currentThread().getContextClassLoader().getClass()
// returns org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader
return CompletableFuture.runAsync(() -> {
    // here returns jdk.internal.loader.ClassLoaders$AppClassLoader
});

第二个ClassLoader不能加载JAXB类。这种行为似乎只存在于Java中,实际上在Java9 9+返回带有主ThreadExecutor之前,但是在Java9之后,它返回一个带有系统ClassLoader的executor。

由于CompletableFuture.runAsync()方法接受Executor作为第二个参数,因此可以在代码中设置所需的Executor。这里是一个可能的解决方案的示例。

首先,定义一个适当的ForkJoinWorkerThreadFactory

代码语言:javascript
复制
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);
        }
    }
}

然后将使用该工厂的Executor传递给runAsync()方法:

代码语言:javascript
复制
return CompletableFuture.runAsync(() -> {
    // now you have the right ClassLoader here
}, getJaxbExecutor());

private ForkJoinPool getJaxbExecutor() {
    JaxbForkJoinWorkerThreadFactory threadFactory = new JaxbForkJoinWorkerThreadFactory();
    int parallelism = Math.min(0x7fff /* copied from ForkJoinPool.java */, Runtime.getRuntime().availableProcessors());
    return new ForkJoinPool(parallelism, threadFactory, null, false);
}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51518781

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档