关于Spring应用异步上传文件MultipartFile时报FileNotFoundException

5,268 阅读2分钟

背景

由于自己项目(springboot后端服务)和文档编辑相关,所以会存在大量的文件上传oss操作,过程中存在有大文件的上传,为了不影响体验,后端服务拿到文件流后直接返回成功,然后交给子线程异步调用oss上传服务。

问题

起初测试什么的根本没发现这个问题,感觉也不是必现的。后来在排查其他问题的时候查看系统日志的时候,偶尔会发现一段java.io.IOException:java.io.FileNotFoundException:/home/admin/appName/.default/temp/tomcat.4504264197870423949.7001/work/Tomcat/Localhost/ROOT/upLoad_ff92855a_13c6_49d9_bbdf_1c062fb9bfd9_00000004.tmp(没有那个文件或目录)的错误。

定位问题

在后端controller入参中是以MultipartFile来包装文件流的。在注释上看的很清楚,会在请求结束后清理掉临时文件。由于我们是调用异步线程来处理最终的文件上传,所以当主线程返回时,清理掉文件时,就会报没有那个文件或目录了。

在springboot的自动配置模块中,有MultipartAutoConfiguration类会默认加载StandardServletMultipartResolver类作为后续bean的元信息。
在初始化web子容器时,会初始化StandardServletMultipartResolver bean作为文件解析器。

在org.springframework.web.servlet.DispatcherServlet#doDispatch方法中处理请求时,最终checkMultipart的调用会调用上述加载的解析器进行处理org.springframework.web.multipart.support.StandardServletMultipartResolver#resolveMultipart

该实现是将request包装成StandardMultipartHttpServletRequest,由于是非懒解析

setMultipartFiles方法是AbstractMultipartHttpServletRequest中的方法,该方法会设值multipartFiles。

在doDispatch方法处理完主线程的请求后,就会清理文件,会委托给org.springframework.web.multipart.support.StandardServletMultipartResolver#cleanupMultipart来处理

如图,StandardMultipartHttpServletRequest是AbstractMultipartHttpServletRequest的子类,isResolved的判断就是AbstractMultipartHttpServletRequest中的属性multipartFiles是否有值,上述分析是有值的,所以就触发了part.delete。

这样的话,在异步线程再去取文件时就报错了!

处理

使用org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.StandardMultipartFile#getBytes方法,该方法会调用org.springframework.util.FileCopyUtils#copyToByteArray生产一个新的字节数组保存数据,以避免原始数据删除之后带来的子线程获取文件失败。