背景
因为本身项目(springboot后端服务)和文档编辑相关,因此会存在大量的文件上传oss操做,过程当中存在有大文件的上传,为了避免影响体验,后端服务拿到文件流后直接返回成功,而后交给子线程异步调用oss上传服务。java
问题
起初测试什么的根本没发现这个问题,感受也不是必现的。后来在排查其余问题的时候查看系统日志的时候,偶尔会发现一段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(没有那个文件或目录)的错误。web
定位问题
在后端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。
这样的话,在异步线程再去取文件时就报错了!spring
处理
使用org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.StandardMultipartFile#getBytes方法,该方法会调用org.springframework.util.FileCopyUtils#copyToByteArray生产一个新的字节数组保存数据,以免原始数据删除以后带来的子线程获取文件失败。