You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Bean混用异步与同步方法时偶发ClassNotFoundException问题咨询

Spring Bean混用异步与同步方法时偶发ClassNotFoundException问题咨询

嗨,我来帮你分析下这个困扰你的偶发异常问题~

先还原下你的场景:
我有一个Spring Bean包含两个方法,一个标注了@Async是异步方法,另一个是普通同步方法。两个方法都在访问通过jaxws-maven-plugin生成的SOAP端点客户端。同步方法运行完全正常,但异步方法偶尔会抛出java.lang.ClassNotFoundException: com.sun.xml.ws.spi.ProviderImpl异常,而且这个问题是偶发的,不是每次都会出现。

附上你提供的示例代码:

@Service
public class MyService {

    @Transactional
    public void createTask(URL url) {
        new TaskService(url).getTaskServicePort().createTask();
    }

    @Async
    @Transactional
    public void getTask(URL url) {
        new TaskService(url).getTaskServicePort().getTask();
    }
}

你的疑问:

  • 这是异步方法调用导致的问题吗?
  • 为什么会偶尔出现这个ClassNotFoundException?
  • 我原本期望两个方法都能正常运行不抛出异常。

补充信息:Java版本17,Spring Boot版本2.7.4


问题根源分析

这个问题确实和异步方法的执行机制直接相关,主要原因有这几点:

  1. 类加载器上下文差异
    Spring的异步方法是通过TaskExecutor在独立的线程池中执行的,这些线程的类加载器可能和原请求线程(比如Tomcat的请求处理线程)的类加载器不一致。同步方法在原线程执行时,类加载器能正确加载com.sun.xml.ws.spi.ProviderImpl;但异步线程的类加载器可能没有包含这个类所在的依赖包,就会偶发触发加载失败。

  2. JAX-WS Provider的加载逻辑
    从Java 11开始,JAX-WS已经完全从JDK中移除了,你需要引入外部的JAX-WS实现(比如Sun的JAX-WS RI)。但Spring异步线程的上下文初始化过程中,可能没有正确让类加载器识别到这个Provider实现,当JDK的ServiceLoader尝试查找JAX-WS Provider时,就会因为类加载器的偶发异常找不到目标类。

  3. 依赖包的可见性问题
    如果你的JAX-WS依赖是通过provided这类非全局可见的scope引入的,异步线程的类加载器可能无法访问到这些依赖,当线程池中的线程第一次初始化时,就可能出现类加载失败的情况。


解决方案建议

针对这些原因,给你几个可行的解决方向:

  • 明确指定JAX-WS Provider
    在项目的src/main/resources/META-INF/services/目录下创建javax.xml.ws.spi.Provider文件,文件内容直接写com.sun.xml.ws.spi.ProviderImpl。这样ServiceLoader就能直接定位到实现类,避免类加载器的查找问题。

  • 统一异步线程的类加载器
    自定义Spring的TaskExecutor,强制异步线程使用当前应用的类加载器。示例代码如下:

@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("MyAsyncThread-");
        // 给异步任务设置和原线程一致的类加载器
        executor.setTaskDecorator(runnable -> {
            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
            return () -> {
                Thread currentThread = Thread.currentThread();
                ClassLoader originalClassLoader = currentThread.getContextClassLoader();
                try {
                    currentThread.setContextClassLoader(contextClassLoader);
                    runnable.run();
                } finally {
                    currentThread.setContextClassLoader(originalClassLoader);
                }
            };
        });
        executor.initialize();
        return executor;
    }
}
  • 调整JAX-WS依赖的scope
    检查你的pom.xml(或build.gradle)中JAX-WS相关依赖的scope,确保是compile或者runtime,而不是provided。示例依赖配置:
<dependency>
    <groupId>com.sun.xml.ws</groupId>
    <artifactId>jaxws-rt</artifactId>
    <version>2.3.5</version>
</dependency>
<dependency>
    <groupId>com.sun.xml.ws</groupId>
    <artifactId>jaxws-api</artifactId>
    <version>2.3.5</version>
</dependency>
  • 复用SOAP客户端实例
    你现在每次调用方法都new TaskService(url),会重复初始化客户端,加重类加载负担。可以考虑把TaskService或者对应的Port实例缓存起来,或者通过Spring注入的方式管理,减少类加载的次数,降低偶发问题的概率。

备注:内容来源于stack exchange,提问作者Pido

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.23 11:47:41