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

ou6hu8tu  于 2023-10-19  发布在  Java
关注(0)|答案(6)|浏览(112)

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

这是怎么回事

我所尝试的

添加JAR到Tomca的CLASSPATH

也许在setenv.sh中将JAR添加到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]

这显然是同一个类,所以显然它已经被两个类装入器装入。我怀疑是系统类加载器和应用程序的类加载器,但是为什么加载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(必须是系统类加载器),另一次是(我假设是从Java中的应用程序加载器)。

查找bug

Searching Tomcat's bug database我找到了#62559.会不会是同一个错误?

添加Tomcat的lib

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

mm9b1k5b

mm9b1k5b1#

分析

首先是一些随机的事实:

  • 如果没有类加载器,JAXBContext::newInstance在查找JAXB实现时将使用线程的上下文类加载器-即使调用newInstance(Class...)也是如此(人们可能会误认为它使用了提供的类示例的加载器)
  • Tomcat构建a small class loader hierarchy以将Web应用程序彼此分离
  • 由于不依赖于模块 java.xml.bind,在Java 9中,JAXB类不被 Bootstrap 或系统类加载器加载

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

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

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

  • 我们添加了JAXB as a regular dependency,因此它由Web应用程序的类加载器加载
  • 就像在Java 8上一样,JAXB搜索系统类加载器,但是,人们看不到应用程序的加载器(只有相反的方式)
  • JAXB无法找到实现,并崩溃了

解决方案

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

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

我们使用了第三个选项,并朝着JAXBContext::newInstance的包接受变体进行了重构。做些琐碎的工作,但解决了问题。

注意事项

User curlals提供了关键的信息,但删除了他们的答案。我希望这不是因为我要求一些编辑。所有的信用/业力都应该归于他们!@curlals:如果你恢复并编辑你的答案,我会接受并投票支持。

41zrol4v

41zrol4v2#

尝试以下内容及其依赖项。参见Maven repository for latest version

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

它还包含Java服务加载器描述符。参见Using JAXB in Java 9+

iswrvxsc

iswrvxsc3#

我在使用Sping Boot (版本2.2.6)时遇到了这个问题,在我的代码的特定部分中嵌入了Tomcat,我使用了CompletableFuture。代码在Java 8中运行良好,相关的单元测试在Java 12中通过。仅当应用程序在使用Java 11或12的Tomcat中执行时才会出现此问题。
我发现这个问题与以下事实有关:在CompletableFutureRunner内部使用了不同的ClassLoader

// 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 9+中,实际上在Java 9之前,ForkJoinPool.common()返回一个Executor,其中包含主ThreadClassLoader,但在Java 9之后,它返回一个executor,其中包含系统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);
        }
    }
}

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

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);
}
lzfw57am

lzfw57am4#

TL;DR

对我来说,一个简单的解决方案就是升级Hibernate版本。
我使用5.2.10.Final版本的Hibernate,它们依赖于JAXB。然而,当我用Tomcat替换undertow时,这种依赖性就消失了。我发现了这个问题,但没有一个答案真正解决了我的问题。当我发现jpa-model-gen是问题所在时,我很快意识到,只有Hibernate依赖性才需要JAXB。升级到一个更高的hibernate版本解决了我的问题。

bweufnob

bweufnob5#

我在使用JAXB时也遇到过类似的问题,
未找到JAXB-API的实现
随机发生,很难复制幸运的是,我发现了一个系统环境,在这个环境中,上述错误是连续的,而其他环境则可以顺利工作。

观察

随着对这个问题的广泛研究,我发现了导致这个问题的一个类加载器问题。我还注意到,

  • JAXB实现对WebappClassLoader可见,它是Tomcat服务器中的类加载器
    *有时它对AppClassLoader等jdk内部类加载器不可见,(尽管它在许多情况下是可见的)

解决方案

JAXBContext对象是线程安全的(而marshaller/unmarshaller不是),并且一旦启动就可以重用。因此,我们认为,
1.我发现了一个使用WebAppClassLoader的线程(即给定线程的上下文类加载器是WebAppClassLoader),并在那里创建了JAXBContext,并存储在一个Map中供以后使用
1.在必要时检索存储的JAXBContext(使用不同类装入器的其他线程)并执行马歇尔/unmarshall任务。这为我节省了一天:)

1cosmwyk

1cosmwyk6#

我为此挣扎了好几天。谢谢你的三个建议。但是,我的要求是我不应该改变任何源代码。我找到了一个非常简单的解决办法。基本上,在Tomcat 10应用服务器上部署war文件之后,我将jar文件jaxb-impl.2.2.6复制到WEB-INF\classes中。从那里使用命令“jar -xvf jaxb-impl.2.2.6”和HOORAY分解jar文件,我试图在Tomcat 10上运行旧JAXB项目的5天噩梦结束了,项目开始工作!
于是,钢琴停止了演奏,又开始演奏。我喜欢这个解决方案,因为我不需要修改任何代码。作为改进的一部分,我现在将在部署war文件期间分解jar文件。

相关问题