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
相关产品推荐
相关产品推荐

