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

SpringBoot 2.4.2 ResourceLoader是否线程安全?并发抛FileNotFoundException如何解决

问题根因

该问题本质是线程上下文类加载器(TCCL)不一致导致的:

  • Spring Boot 打包成jar运行时,会使用自定义的LaunchedURLClassLoader加载BOOT-INF/classes下的类和资源、以及BOOT-INF/lib下的嵌套jar内容,只有这个类加载器能识别Spring Boot的嵌套jar结构。
  • 应用主线程启动时,Spring Boot已经自动将主线程的TCCL设置为LaunchedURLClassLoader,所以串行调用时读取资源正常。
  • 并行流默认使用JVM全局共享的ForkJoinPool.commonPool(),自定义线程池如果没有特殊配置,线程创建时的TCCL是系统类加载器,这两个类加载器都无法识别Spring Boot的嵌套jar结构,因此找不到classpath下的资源。
  • 单元测试环境下类路径是扁平结构,没有嵌套jar,系统类加载器也可以直接读取到资源,因此无法复现问题。
解决方案

以下三个方案均可解决问题,推荐优先使用方案3:

方案1:强制使用当前Bean的类加载器加载资源

直接使用当前类的类加载器(该类由Spring加载,必然是LaunchedURLClassLoader)读取资源,完全不依赖线程的TCCL:

@Service
@RequiredArgsConstructor
public class BeanWithResourceLoader{
    @SneakyThrows
    private String readResource() {
        return IOUtils.toString(
          // 强制使用当前类的类加载器读取资源
          BeanWithResourceLoader.class.getResourceAsStream("/some_resource.json"), 
          StandardCharsets.UTF_8
        );
    } 
}

方案2:为线程池配置正确的TCCL

如果需要保留ResourceLoader的使用逻辑,可以在自定义线程池、自定义ForkJoinPool的线程工厂中,统一为创建的线程设置Spring的类加载器:

@Bean
public Executor customThreadPool() {
    ClassLoader springClassLoader = BeanWithResourceLoader.class.getClassLoader();
    ThreadFactory threadFactory = runnable -> {
        Thread thread = new Thread(runnable);
        // 为新创建的线程设置正确的TCCL
        thread.setContextClassLoader(springClassLoader);
        return thread;
    };
    // 并行流也可以使用该自定义线程工厂创建ForkJoinPool使用
    return new ThreadPoolExecutor(10, 20, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), threadFactory);
}

方案3:提前缓存静态资源内容

classpath下的资源一般是静态不变的,可以在Bean初始化阶段一次性加载到内存缓存,后续调用直接返回缓存值,既规避了类加载器问题,也减少了重复IO开销:

@Service
public class BeanWithResourceLoader{
    private String cachedResource;

    @PostConstruct
    @SneakyThrows
    public void initResource() {
        cachedResource = IOUtils.toString(
          BeanWithResourceLoader.class.getResourceAsStream("/some_resource.json"), 
          StandardCharsets.UTF_8
        );
    }

    private String readResource() {
        return cachedResource;
    } 
}

内容的提问来源于stack exchange,提问作者Capacytron

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 07:24:01