一、问题描述
在使用Spring Boot做文件上传的过程中,遇到上传文件报错 FileNotFoundException 问题,查了一圈,都是说要配置上传文件路径问题,经过仔细的分析和测试,发现不是配置路径的问题 (在主线程中,没配置路径,可以正常实现上传!),而是用了异步上传的问题导致的。
报错信息如下:
- java.io.IOException: java.io.FileNotFoundException: C:\Users\xxx\AppData\Local\Temp\tomcat.7137654154096270302.8080\work\Tomcat\localhost\ROOT\upload_f6b6398b_fe9b_41af_9579_4a835264fef3_00000000.tmp (系统找不到指定的文件。)
- at org.apache.catalina.core.ApplicationPart.write(ApplicationPart.java:122)
-
二、模拟实现
1、使用 异步线程上传文件,抛出 FileNotFoundException 异常
- /**
- * @Description: 演示 异步上传 - file not found 问题 --- simple 版
- * @param file
- * @return void
- * @version v1.0
- * @author wu
- * @date 2022/9/25 20:09
- */
- @RequestMapping("/up")
- public String up(MultipartFile file){
- ExecutorService executor = Executors.newSingleThreadExecutor();
- executor.submit(()->{
- try {
- Thread.sleep(1000L);
- file.transferTo(new File("D:/",new Date().getTime()+file.getOriginalFilename()));
- } catch (IOException | InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+" ==> 异步上传完成!");
- });
-
- return file.getOriginalFilename()+":文件上传成功!";
- }
1.1、运行结果:
- java.io.IOException: java.io.FileNotFoundException: C:\Users\xxx\AppData\Local\Temp\tomcat.7137654154096270302.8080\work\Tomcat\localhost\ROOT\upload_f6b6398b_fe9b_41af_9579_4a835264fef3_00000000.tmp (系统找不到指定的文件。)
- at org.apache.catalina.core.ApplicationPart.write(ApplicationPart.java:122)
- at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile.transferTo(StandardMultipartHttpServletRequest.java:255)
- at com.runcode.springboottourist.upload.web.UploadAsyncController.lambda$up$2(UploadAsyncController.java:187)
- at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
- at java.util.concurrent.FutureTask.run(FutureTask.java:266)
- at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
- at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
- at java.lang.Thread.run(Thread.java:745)
-
- pool-4-thread-1 ==> 异步上传完成!
三、问题解决
1、原来 MultipartFile 对象在文件上传的时候,会生成 临时文件,此时生成的临时文件在主线程中;而异步线程在操作 MultipartFile 对象时,此时主线程中的临时文件,将会被 spring 给删除了,也就造成异常 FileNotFound !
2、办法一:在主线中获取到输入流对象,可以解决 文件被删除的问题: InputStream inputStream = file.getInputStream();
- /**
- * @Description: 解决异步线程中 file not found 问题
- *
主线程中: file.getInputStream(); , 应该是会生成临时文件的 - * @param file
- * @return java.lang.String
- * @version v1.0
- * @author wu
- * @date 2022/9/25 20:43
- */
- @RequestMapping("/up/fix")
- public String upFix(MultipartFile file) throws IOException {
- ExecutorService executor = Executors.newSingleThreadExecutor();
- /**
- * 1. 可以解决 异步线程中 file not found 的问题
- * file.getInputStream(); 这行代码不能删掉。
- */
- InputStream inputStream = file.getInputStream();
- executor.submit(()->{
- try {
- Thread.sleep(1000L);
- file.transferTo(new File("D:/",new Date().getTime()+file.getOriginalFilename()));
- } catch (IOException | InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+" ==> 异步上传完成!");
- });
-
- return file.getOriginalFilename()+":文件上传成功!";
- }
3、办法二:使用 inputStream流作为入参
- @RequestMapping("/up/fix2")
- public String upFix2(MultipartFile file) throws IOException {
- ExecutorService executor = Executors.newSingleThreadExecutor();
- /**
- * 使用 inputStream 作为入参,解决 file not found 问题
- */
- InputStream inputStream = file.getInputStream();
- String filename = file.getOriginalFilename();
- executor.submit(()->{
- try {
- Thread.sleep(1000L);
-
- // 使用 spring的 FileCopyUtils
- FileCopyUtils.copy(inputStream, Files.newOutputStream(new File("D:/", new Date().getTime() + filename).toPath()));
- } catch (IOException | InterruptedException e) {
- e.printStackTrace();
- }finally {
- try {
- inputStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- System.out.println(Thread.currentThread().getName()+" ==> 异步上传完成!");
- });
-
- return file.getOriginalFilename()+":文件上传成功!";
- }
四、总结
1、本文以极简的示例,模拟出现异常的情况,完整的 Spring Boot 文件上传下载,可以参考:Spring Boot 实现文件上传和下载 以及上传后访问文件_HaHa_Sir的博客-CSDN博客_springboot上传文件访问
2、异步线程操作,总会遇到一些莫名其妙的问题,需要静下心来,认真思考,多尝试,多反思: 在 主线程中操作,会出现这个情况吗?
3、办法一 和 办法二 很类似,其中 办法一,获取到 inputStream对象后,也没有进行后续的操作,不知道为啥是可以的 ...
4、实际开发中,需要使用 异步文件上传时候,建议使用 【办法二】,以参数的形式传递变量到异步线程中 。 因为【办法一】中 inputStream 变量,没有被使用,会被一些 IDE 提示,无效的变量,建议删除; 若真的删除了 ... 就会有 奇怪的错误出现~~
关于异步线程可能出现的问题,了解更多:
https://blog.csdn.net/HaHa_Sir/article/details/127044489
Spring Boot 实现文件上传和下载 以及上传后访问文件_HaHa_Sir的博客-CSDN博客_springboot上传文件访问