我想分享一些可能对某人有所帮助的小发现。
我使用spring的MultipartFile上传大文件,担心spring会将内容存储在内存中。因此,我决定使用getInputStream() 方法,希望这会将文件直接流式传输到所需位置:
@PostMapping("/upload")
public ResponseEntity<?> uploadFile(@RequestPart MultipartFile file) throws FileNotFoundException, IOException{
FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(new File("/storage/upload/", file.getOriginalFilename())));
return ResponseEntity.ok("Saved");
}
当我用 2GB 文件测试控制器时,需要很长时间才能点击控制器方法。于是我调试了一下,发现spring/Tomcat在处理给控制器之前先把文件保存在一个临时文件夹中。这意味着,当您调用 getInputStream() 时,它会返回一个 FileInputStream 指向存储在文件系统上的文件,而不是直接从客户端浏览器流式传输。
换句话说,调用FileCopyUtils.copy()很慢,因为它将整个文件复制到另一个位置,然后删除临时文件,使得完成请求需要两倍的时间。
我调查并发现您可以禁用弹簧功能并手动处理多部分请求,但有点复杂且容易出错。因此,进一步挖掘,我发现MultipartFile 有一个名为transferTo 的方法,它实际上将临时文件移动到所需的位置。我测试了它,它是瞬时的。我的代码是这样的:
@PostMapping("/upload")
public ResponseEntity<?> uploadFile(@RequestPart MultipartFile file) throws FileNotFoundException, IOException{
file.transferTo(new File("/storage/upload/", file.getOriginalFilename()));
return ResponseEntity.ok("Saved");
}
结论,如果您只想将文件上传到特定目录/文件,您可以使用此解决方案,它与手动流式传输文件一样快。
重要提示:有两种transferTo() 方法,一种接收Path,另一种接收File。不要使用收到Path 的那个,因为它会复制文件并且速度很慢。
EDIT1:
我使用HttpServletRequest 测试了该解决方案,但它仍会存储一个临时文件,除非您设置弹簧配置spring.servlet.multipart.enabled = false。使用MultipartHttpServletRequest 的解决方案也是如此。
我发现使用我找到的解决方案的三个主要好处:
- 很简单
- 它允许您一次处理多个文件,您只需在控制器方法中添加多个
@RequestPart MultipartFile
- 它允许您轻松处理包含文件的响应正文
public ResponseEntity<?> uploadFile(@RequestPart @Valid MyCustomPOJO pojo, @RequestPart MultipartFile file1, @RequestPart MultipartFile file2, @RequestPart MultipartFile file3)
这是我为测试一些概念而创建的测试项目的 URL,包括这个:
https://github.com/noschang/SpringTester