Spring Boot 文件上传 报错:FileNotFoundException Spring 异步文件上传 FileNotFoundException

Source

 Spring Boot 文件上传 报错:FileNotFoundException Spring Boot异步文件上传 FileNotFoundException

一、问题描述

        在使用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 问题
 * <br> 主线程中: 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上传文件访问