parallelStream()使用JAXB-API导致ClassNotFoundException [英] parallelStream() causing ClassNotFoundException with JAXB-API
问题描述
在我们的应用程序中,有时会遇到以下异常:
In our application we sometimes get the following exception:
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]
我们已经发现只有在使用 Collection.parallelStream()
时才会发生这种情况,但在使用 Collection时不会发生.stream()
。
We already found out that this happens only if we use Collection.parallelStream()
but not if we use Collection.stream()
.
我们看到JAXB使用 Thread.currentThread()。getContextClassLoader()
用于加载类。我们还看到,当使用 parallelStream()
时,用于执行命令的线程正在使用不同的类加载器。有时是 org.apache.catalina.loader.WebappClassLoader
,有时是 jdk.internal.loader.ClassLoaders.AppClassLoader
。
We saw that JAXB uses Thread.currentThread().getContextClassLoader()
for loading classes. We also saw, that when using parallelStream()
, the threads for executing our command are using different class loaders. Sometimes it's a org.apache.catalina.loader.WebappClassLoader
, and sometimes it's a jdk.internal.loader.ClassLoaders.AppClassLoader
.
现在看来, AppClassLoader
不了解JAXB依赖关系,而 WebappClassLoader
。
And it now seems, that the AppClassLoader
does not know about the JAXB dependencies, whereas the WebappClassLoader
does.
我们正在使用Java 11和以下Maven依赖项:
We are using Java 11 and the following Maven dependencies:
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
任何想法可能出什么问题吗? AppClassLoader
怎么可能不了解我们的依赖关系?
Any idea what could be wrong? How can it be, that the AppClassLoader
does not know about our dependencies?
推荐答案
我们有不同的经历。问题是tomcat的类加载器与每次大战不同(我认为我们在Spring Boot应用程序中看到了类似的东西; void-main与应用程序运行器的情况有所不同)。
We had a different experience. The issue was that tomcat has a different classloader than each war (I think we're seeing something similar with spring boot apps; void-main was different than that of application-runner)..
无论哪种方式,问题都是 bootstrap classloader无法访问您应用程序中的jar,因此没有jaxb。因此,如果您曾经启动线程(例如 StreamXX.parallel()
或 ForkJoinPool的
),那么这些线程将来自引导类加载器,而不是应用程序的类加载器。因此,如果您的后台任务第一次加载JAXB,它们将运行 ForkJoinPoolThread
.commonThreadPool() getClass( ).getContextClassLoader()。getResourceAsStream( xxxx)
不会找到资源。
Either way, the issue is that the "bootstrap" classloader doesn't have access to the jars in your application - and thus NO jaxb. So if you ever launch a thread (like ForkJoinPoolThread
from StreamXX.parallel()
or ForkJoinPool.commonThreadPool()
) then THOSE threads will be from the bootstrap classloader, NOT your application's classloader.. So if the FIRST time your background task loads JAXB, they will run getClass().getContextClassLoader().getResourceAsStream("xxxx")
and will not find the resource.
我们的解决方案是在显式线程中启动所有后台任务-pool并具有一个显式的线程池工厂。thread-pool-factory从调用线程(由war或spring-boot上下文初始化的线程)中捕获类加载器。这个类加载器将有jaxb和朋友。。因此,从该线程池工厂启动的每个线程都有一个显式的 thr.setContextClassLoader(globalCL);
....
Our solution was to launch all background tasks in an explicit thread-pool and have an explicit thread-pool factory.. The thread-pool-factory captures the classloader from the invoking thread (the one initialized by the war or the spring-boot context). This classloader WILL have jaxb and friends.. So now each thread launched from this thread-pool factory has an explicit thr.setContextClassLoader(globalCL);
....
问题(通过黑客入侵)
这篇关于parallelStream()使用JAXB-API导致ClassNotFoundException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!