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

SpringBoot中Executor Service转发MultipartFile出现空字节问题

异步转发MultipartFile后文件内容为空的问题分析与解决

问题场景

调用流程为Client -> Service1 -> Service2,需从Service1向Service2转发MultipartFile。Service1使用ExecutorService异步发送文件,代码及现象如下:

Service1代码

@RestController
@RequestMapping("service1")
public class TestController {

    @PostMapping(value = "upload", produces = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public ResponseEntity<Void> upload(@RequestPart MultipartFile file) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.execute(() -> {
            sendFile(file);
        });
        return ResponseEntity.ok().build();
    }

    private void sendFile(MultipartFile file) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Content-Type", "multipart/form-data");

        MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<>();
        parameters.add("file", file.getResource());

        HttpEntity requestEntity = new HttpEntity<>(parameters, headers);

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.exchange("http://localhost:8081/service2/upload",  HttpMethod.POST,  requestEntity, String.class);
    }
}

Service2代码

@RestController
@RequestMapping("service2")
public class TestController {
    Logger logger = LoggerFactory.getLogger(TestController.class);

    @PostMapping(value = "upload", produces = {MediaType.MULTIPART_FORM_DATA_VALUE}, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public ResponseEntity<Void> upload(@RequestPart MultipartFile file) {
        try {
            logger.info("fileName: {}", file.getOriginalFilename());
            logger.info("fileBytes: {}", file.getBytes());
            return ResponseEntity.ok().build();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

异常现象

Service2接收到的文件文件名正确,但文件字节数为0;移除ExecutorService直接同步调用sendFile时功能正常。日志输出:

2022-11-23 11:14:40.277  INFO 26324 --- [nio-8081-exec-4] c.b.b.controller.TestController          : fileName: testFile.txt
2022-11-23 11:14:40.277  INFO 26324 --- [nio-8081-exec-4] c.b.b.controller.TestController          : fileBytes: []

原因分析

Spring的MultipartFile是请求作用域对象:

  • 请求处理期间(Service1的upload方法执行过程中),MultipartFile对应的输入流和临时文件有效;
  • 当upload方法返回ResponseEntity后,请求处理完成,Spring会自动关闭MultipartFile的底层输入流并清理临时文件;
  • 异步执行时,主线程(请求线程)直接返回导致请求提前结束,异步线程启动时文件流已关闭、临时文件已被清理,因此仅能读取到内存中存储的文件名,文件内容为空。

解决方案

核心思路:在请求结束前(主线程中)将MultipartFile内容读取到内存或自定义临时文件,再交给异步线程处理,以下是具体实现:

方案1:提前读取文件内容到内存

第一步:配置ExecutorService为Bean(避免重复创建)

@Configuration
public class ExecutorConfig {
    @Bean
    public ExecutorService fixedThreadPool() {
        return Executors.newFixedThreadPool(10);
    }
}

第二步:修改Service1的TestController

@RestController
@RequestMapping("service1")
public class TestController {

    @Autowired
    private ExecutorService fixedThreadPool;

    @PostMapping(value = "upload", produces = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public ResponseEntity<Void> upload(@RequestPart MultipartFile file) {
        // 主线程读取文件内容到内存
        byte[] fileContent;
        String fileName = file.getOriginalFilename();
        try {
            fileContent = file.getBytes();
        } catch (IOException e) {
            throw new RuntimeException("读取上传文件失败", e);
        }

        // 异步线程使用内存中的文件内容发送
        fixedThreadPool.execute(() -> {
            sendFile(fileName, fileContent);
        });
        return ResponseEntity.ok().build();
    }

    private void sendFile(String fileName, byte[] fileContent) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Content-Type", "multipart/form-data");

        MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<>();
        // 用ByteArrayResource包装内存内容,指定文件名
        parameters.add("file", new ByteArrayResource(fileContent) {
            @Override
            public String getFilename() {
                return fileName;
            }
        });

        HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(parameters, headers);

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.exchange("http://localhost:8081/service2/upload",
                HttpMethod.POST,
                requestEntity,
                String.class);
    }
}

方案说明

  • 主线程提前读取文件内容到内存,避免请求结束后文件资源被清理;
  • ByteArrayResource包装字节数组并指定文件名,保证Service2能正确解析完整的文件信息;
  • 线程池配置为Bean,避免重复创建提升资源利用率。

其他可选方案(不推荐)

  • 修改Spring的MultipartResolver配置,延长临时文件存活时间或禁止自动删除,但易导致临时文件堆积引发磁盘泄漏;
  • 使用Spring的@Async注解替代手动创建线程池,但核心仍需保证异步执行前已读取文件内容。

内容的提问来源于stack exchange,提问作者Nguyễn Giang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 09:46:40