Spring Boot图片上传临时文件清理失败问题排查与修复
问题:Spring Boot文件上传后无法清理临时文件的原因与解决方法
问题场景
开发了React前端+Spring Boot后端的图片上传应用,用于将图片保存到指定文件夹,核心后端代码如下:
@PostMapping(value = "/upload") public ResponseEntity<?> uploadImage(@RequestParam("user") String user, @RequestParam("image") MultipartFile file) { try { System.out.println("-------------------------------------------------------------------"); this.process(IOUtils.toByteArray(file.getInputStream()), user, file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."))); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return new ResponseEntity<>("Uploaded", HttpStatus.OK);//userService.uploadImageFile(user, file); } @Async public void process(byte[] bs, String user, String ext) throws IOException { String fName = user.substring(0, user.lastIndexOf("@")).concat(ext); // Full path String filePath = path + File.separator + fName; System.out.println(fName + " and " + filePath); File f = new File(path); if (!f.exists()) { f.mkdir(); } File convertedFile = new File(filePath); FileOutputStream fOS = new FileOutputStream(convertedFile); System.out.println("-----Started------"); fOS.write(bs); fOS.close(); System.out.println("-----closed------"); }
上传过程中出现如下错误:
org.springframework.web.multipart.support.StandardServletMultipartResolver[0;39m: Failed to perform cleanup of multipart items java.io.UncheckedIOException: Cannot delete D:\JS Projects\myapp\BackEnd\UserLoginSignUp\images\work\Tomcat-2\localhost\ROOT\upload_d31827cc_51e6_4901_b60e_6afdd0ee8a99_00000004.tmp at org.apache.tomcat.util.http.fileupload.disk.DiskFileItem.delete(DiskFileItem.java:431) at org.apache.catalina.core.ApplicationPart.delete(ApplicationPart.java:53) at org.springframework.web.multipart.support.StandardServletMultipartResolver.cleanupMultipart(StandardServletMultipartResolver.java:134) at org.springframework.web.servlet.DispatcherServlet.cleanupMultipart(DispatcherServlet.java:1251) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1108) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) at javax.servlet.http.HttpServlet.service(HttpServlet.java:555) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) at javax.servlet.http.HttpServlet.service(HttpServlet.java:623) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.base/java.lang.Thread.run(Thread.java:833)
错误原因
- 异步处理导致资源冲突:
process方法标记了@Async,主线程会直接返回响应并触发Spring的临时文件清理逻辑,但此时异步线程可能还在占用临时文件的资源,导致文件被锁定无法删除。 - 输入流未正确释放:手动读取
file.getInputStream()后未确保流被及时关闭,导致临时文件的文件句柄未释放,Tomcat无法完成清理。
解决方法
方法一:主线程中完成流读取与关闭,再传递字节数组给异步方法
使用try-with-resources自动关闭输入流,确保主线程中就释放临时文件资源,异步线程仅处理内存中的字节数组:
@PostMapping(value = "/upload") public ResponseEntity<?> uploadImage(@RequestParam("user") String user, @RequestParam("image") MultipartFile file) { try (InputStream inputStream = file.getInputStream()) { byte[] bs = IOUtils.toByteArray(inputStream); String ext = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); this.process(bs, user, ext); } catch (IOException e) { e.printStackTrace(); return new ResponseEntity<>("Upload failed", HttpStatus.INTERNAL_SERVER_ERROR); } return new ResponseEntity<>("Uploaded", HttpStatus.OK); }
方法二:异步方法中直接使用MultipartFile的transferTo方法
Spring推荐使用transferTo处理文件上传,它会自动管理资源的打开与关闭,避免手动流操作的问题:
@PostMapping(value = "/upload") public ResponseEntity<?> uploadImage(@RequestParam("user") String user, @RequestParam("image") MultipartFile file) { try { String ext = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); this.process(file, user, ext); } catch (Exception e) { e.printStackTrace(); return new ResponseEntity<>("Upload failed", HttpStatus.INTERNAL_SERVER_ERROR); } return new ResponseEntity<>("Uploaded", HttpStatus.OK); } @Async public void process(MultipartFile file, String user, String ext) throws IOException { String fName = user.substring(0, user.lastIndexOf("@")).concat(ext); String filePath = path + File.separator + fName; File f = new File(path); if (!f.exists()) { f.mkdirs(); // 支持创建多级目录 } file.transferTo(new File(filePath)); }
方法三:配置延迟解析Multipart文件(备选方案)
在application.properties中添加配置,延迟临时文件的清理时机,确保异步线程完成后再执行清理:
spring.servlet.multipart.resolve-lazily=true
内容的提问来源于stack exchange,提问作者Abhishek Kumar
相关产品推荐
相关产品推荐

