MultipartFile的transferTo方法的坑

article/2025/10/7 3:29:23

前言 最近用SpringBoot写文件上传功能,使用参数绑定之后确实是非常的方便了。但是,项目部署就出现了问题,搞得我一脸懵逼。后来,才发现是因为我使用了相对路径导致的,这个绝对是一个坑人的地方,不过也说明需要学习的东西还有很多!

案例再现

@PostMapping("/uploadFile")
public String uploadImg(@RequestParam("file") MultipartFile file, @RequestParam("equipmentId") String equipmentId) {
String baseDir = "./imgFile";  // 这里不能直接使用相对路径if (!file.isEmpty()) {String name = file.getOriginalFilename();String prefix = name.lastIndexOf(".") != -1 ? name.substring(name.lastIndexOf(".")) : ".jpg";String path = UUID.randomUUID().toString().replace("-", "") + prefix;try {// 这里代码都是没有问题的File filePath = new File(baseDir, path);// 第一次执行代码时,路径是不存在的logger.info("文件保存路径:{},是否存在:{}", filePath.getParentFile().exists(), filePath.getParent());if (!filePath.getParentFile().exists()) {   // 如果存放路径的父目录不存在,就创建它。filePath.getParentFile().mkdirs();}// 如果路径不存在,上面的代码会创建路径,此时路径即已经创建好了logger.info("文件保存路径:{},是否存在:{}", filePath.getParentFile().exists(), filePath.getParent());// 此处使用相对路径,似乎是一个坑!// 相对路径:filePath// 绝对路径:filePath.getAbsoluteFile()logger.info("文件将要保存的路径:{}", filePath.getPath());file.transferTo(filePath);logger.info("文件成功保存的路径:{}", filePath.getAbsolutePath());return "上传成功";} catch (Exception e) {logger.error(e.getMessage());}}return "上传失败";
}

我在日志中打印了路径的位置,显示是没有问题,当时一旦执行到file.transferTo(filePath);就会产生一个FileNotFoundException,但是我前面的代码是执行了,并且创建了一个文件夹的。

Postman测试截图
在这里插入图片描述

日志输出


2020-11-27 10:15:06.519  INFO 5200 --- [nio-8080-exec-1] r.controller.LearnController             : 文件保存路径:false,是否存在:.\imgFile
2020-11-27 10:15:06.521  INFO 5200 --- [nio-8080-exec-1] r.controller.LearnController             : 文件保存路径:true,是否存在:.\imgFile
2020-11-27 10:15:06.521  INFO 5200 --- [nio-8080-exec-1] r.controller.LearnController             : 文件将要保存的路径:.\imgFile\684918a520684801b658c85a02bf9ba5.jpg
2020-11-27 10:15:06.522 ERROR 5200 --- [nio-8080-exec-1] r.controller.LearnController             : java.io.FileNotFoundException: C:\Users\Alfred\AppData\Local\Temp
\tomcat.8080.2388870592947355119\work\Tomcat\localhost\ROOT\.\imgFile\684918a520684801b658c85a02bf9ba5.jpg (系统找不到指定的路径。)

注意: 这里虽然没有什么头绪,当时观察日志可以发现,程序试图将文件保存到一个很奇怪的目录下,当是这个目录和前面那个filePath已经没有关系了,这里是一个疑点!

执行之后代码所在目录下面已经创建了一个imgFile目录
在这里插入图片描述

imgFile文件夹中是空的,因为执行transferTo时抛出了异常
在这里插入图片描述

修改此处传如的参数,改为文件的绝对路径

file.transferTo(filePath.getAbsoluteFile());

Postman测试截图

上传成功!
在这里插入图片描述
执行之后代码所在目录下面已经创建了一个imgFile目录
在这里插入图片描述

imgFile文件夹中已经有了上传的图片
在这里插入图片描述

原因分析

上面失败与成功只是因为路径所代表的是相对路径和绝对路径的区别。这就说明是MultiparFile的transferTo方法有问题了。让我们加一个断点,调试走一波!debug!

补充一个debug的小知识:

debug tips:
step into: 单步执行,遇到子函数就进入并且继续单步执行(F5)
step over: 在单步执行时,在函数内遇到子函数时不会进入子函数内单步执行,而是将子函数整个执行完再停止,也就是把子函数整个作为一步(F6)
step return: 在单步执行到子函数内时,用step return就可以执行完子函数余下部分,并返回上一层。
setp out: 效果同 step return。

我这里只给file.transferTo(filePath.getAbsoluteFile());这行代码加了断点,这里我给出调试中最重要的两个步骤:

调试中代码的执行流程是:
但代码进入 transferTo 后,然后执行 this.part.write(dest.getpath)方法,进入 write 方法内部,到这里就可以得到我们的答案了!

@Override
public void transferTo(File dest) throws IOException, IllegalStateException {this.part.write(dest.getPath());if (dest.isAbsolute() && !dest.exists()) {// Servlet 3.0 Part.write is not guaranteed to support absolute file paths:// may translate the given path to a relative location within a temp dir// (e.g. on Jetty whereas Tomcat and Undertow detect absolute paths).// At least we offloaded the file from memory storage; it'll get deleted// from the temp dir eventually in any case. And for our user's purposes,// we can manually copy it to the requested location as a fallback.FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest.toPath()));}
}@Override
public void write(String fileName) throws IOException {File file = new File(fileName);if (!file.isAbsolute()) {file = new File(location, fileName);}try {fileItem.write(file);} catch (Exception e) {throw new IOException(e);}
}

这个write方法,会判断传入的参数是否是相对路径,如果是相对路径,它会自己给我们拼接一个父路径! 所以你应该知道那个奇怪的路径是哪里来的了吧!

C:\Users\Alfred\AppData\Local\Temp\tomcat.8080.2388870592947355119\work\Tomcat\localhost\ROOT\.\imgFile\684918a520684801b658c85a02bf9ba5.jpg

好了,大概可以理清了,这是因为transferTo的参数,如果是相对路径的话,程序会自己拼接一个父路径,因为我指定的相对路径中带有一个不存在的路径,如果尝试保存是会失败的。但是如果你传入的参数只是一个文件名,那应该就能保存成功。但是这样,取文件的时候,又会遇到问题了,你可能都不知道文件在哪里!

补充

这里还有一个很有意思的地方,如果我的相对路径中不使用 . 开头,而只是以 / 开头,那么又会产生一个好玩的情况了。第一种情况就算刚才那样的,这里我们来讨论第二种情况,这种情况在Windows系统中还是同第一种一样的错误,但是在Linux系统中,它是可以正常执行的。如果你了解一点两个系统的知识的话,就应该知道Linux系统的根路径就是 /,所以以 / 开头的路径即是绝对路径。所以这也算是程序跨平台需要考虑的问题了,如果不了解Linux的话,你可能不会明白,这里我给出一个验证程序实际测试一下。


Windows系统和Linux系统运行结果不同的代码。

import java.io.File;
import java.io.IOException;public class OSMain {public static void main(String[] args) {String path1 = "./hehe";String path2 = "/haha";File file1 = new File(path1);File file2 = new File(path2);System.out.println("file1: " + file1 + " file1是绝对路径吗? " + file1.isAbsolute());System.out.println("file2: " + file1 + " file2是绝对路径吗? " + file2.isAbsolute());try {System.out.println(file1.getCanonicalPath());System.out.println(file2.getCanonicalPath());} catch (IOException e) {e.printStackTrace();}}	
}

Windows运行结果
在这里插入图片描述

Linux运行结果
这里需要一个Linux环境,但是我的电脑上面没有,虽然我买了一台阿里云服务器。但是为了这么小小的一段代码登陆阿里云服务器去执行,我又嫌麻烦。还好我想到了一个更加巧妙的方法!

以前,知乎上面曾经有一个问题是关于菜鸟教程的,然后菜鸟教程的作者亲自出来回答了问题,并且贴了一张图片——菜鸟教程技术结构图谱
在这里插入图片描述

这个图片本身其实是涉及到了很多的,但是我们这里只关注一个就是在线代码提交执行,看到那只可爱的鲸鱼了吗?对,它就是docker。Docker里面就是一个完整的操作系统,并且是Linux系统!

好了,打开 菜鸟教程–>java教程–>随便找一个运行实例,进去删除原来的代码,复制我这个代码上去执行,输出结果!嘿嘿
在这里插入图片描述

注意: 有些在线代码执行是屏蔽了某些包的,所以有的也不一定是可以执行成功的,如果这里作者对在线代码提交执行做了那种限制,我们还是只能老老实实的去Linux系统上面执行了。不过,有时候站在巨人的肩膀上,真的是挺轻松的!


http://chatgpt.dhexx.cn/article/QeYZah3h.shtml

相关文章

通过零拷贝进行有效的数据传输(java、c)

目录 日期转移:传统方法 数据传输:零复制方法 构建文件服务器 性能比较 概要 相关阅读 许多Web应用程序提供大量的静态内容,这相当于从磁盘上读取数据并将完全相同的数据写回响应套接字。该活动似乎需要较少的CPU活动,但效率…

NIO中的零拷贝--transferTo

1、我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是 重复的(只有 kernel buffer 有一份数据)。 2、零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下 文切…

使用transferTo上传文件容器为jetty的问题

报错 springboot 内嵌的 tomcat替换jetty 9.4.44 后出现的问题。 jetty容器使用transferTo 上传问题会提示找不到文件。 ava.io.FileNotFoundException: C:\Users\XXX\AppData\Local\Temp\D:\workspace\1675246694267.xlsx(文件名、目录名或卷标语法不正确。) 跟进代码发现 是下…

Java很简单的文件上传(transferTo方式)

采用file.Transto 来保存上传的文件,代码简单,速度快. package com.springbootemaildemo.controller;import com.springbootemaildemo.entity.ResponseEntity; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import…

动态规划题目2

动态规划 跳跃游戏 VI (滑动窗口动态规划)丑数统计字典序元音字符串的数目最长重复子串分隔数组以得到最大和最低票价回文子串最长重复子数组最长回文子序列摆动序列旋转函数统计各位数字都不同的数字个数 (动规排列组合)最大整除子集猜数字大小 II超级丑数预测赢家栅栏涂色丑数…

动态规划专题精讲1

致前行的人: 要努力,但不要着急,繁花锦簇,硕果累累都需要过程! 前言: 本篇文章为大家带来一种重要的算法题,就是动态规划类型相关的题目,动态规划类的题目在笔试和面试中是考察非常高…

Java实现动态规划经典题目

动态规划入门请看: DP动态规划专题(一)动态规划基本模型 前言 【说明】 关于动态规划的见解:动规和递归有很多相似的地方,最显著的特征可以说是阶段性,二者都有很明显的阶段划分,所以&#xf…

十一、动态规划题目相关

学习来源: 代码随香录:https://www.programmercarl.com/ labuladong算法:https://labuladong.github.io/algo/ 动态规划 动态规划五部曲 确定dp数组(dp table)以及下标的含义 确定递推公式 dp数组如何初始化 确定遍历…

动态规划题目汇总

文章目录 序言题目一:古生物血缘远近判定题目二:迷宫I题目三:迷宫II题目四:出界的路径数题目五:最长公共字串题目六:最长递增子序列题目七:递增的三元子序列题目八:最长回文字串题目…

动态规划经典例题:不同路径

力扣上比较简答的一道动态规划题目。 方程&#xff1a; class Solution { public:int uniquePaths(int m, int n) {const int M m;const int N n;int dp[M][N];memset(dp, 0, sizeof(dp));for (int i 0; i < m; i) dp[i][0] 1;for (int i 0; i < n; i) dp[0][i] 1;…

动态规划算法与典型例题

目录 前言 一、动态规划要素&#xff08;条件&#xff09; 二、动态规划算法设计步骤 三、复杂度分析 四、典型例题1——游艇租聘 五、典型例题2——0-1背包问题 六、典型例题3——跳台阶问题 七、典型例题4——强盗抢劫问题 总结 前言 动态规划也是一种分治思想&…

动态规划问题经典例题

目录 前言一、字符串分割二、三角矩阵的最小路径和三、路径总数四、最小路径和五、背包问题六、 回文串分割七、编辑距离八、不同的子序列 前言 DP&#xff08;Dynamic Programming&#xff09;定义&#xff1a; 动态规划是分治思想的延伸&#xff0c;通俗一点来说就是大事化小…

动态规划经典题目总结

微信公众号 在算法中&#xff0c;动态规划题目算是比较经典的一类题目。在找工作中&#xff0c;不管是笔试&#xff0c;还是面试&#xff0c;我们经常会遇到用动态规划来解决问题的情况&#xff0c;有时候面试官还需要我们现场手写出动态规划解法的代码。因此&#xff0c;在求职…

【动态规划】经典例题

一.动态规划三要素 1.最优子结构 2.状态转移方程 &#xff08;核心&#xff09;&#xff08;一般用打表找出规律&#xff09; 3.边界值 二.背包问题 &#xff08;一.题目&#xff09; 1.1题目描述 现在有一个背包但容量有限&#xff0c;最多只能装m千克宝石!有n个宝石&…

【动态规划专栏】--基础-- 动态规划经典题型

目录 动态规划 动态规划思维&#xff08;基础&#xff09; 状态表示&#xff08;最重要&#xff09; 状态转移方程&#xff08;最难&#xff09; 初始化&#xff08;细节&#xff09; 填表顺序&#xff08;细节&#xff09; 返回值&#xff08;结果&#xff09; 1、第 …

动态规划入门详解 内含12道经典动态规划编程题

动态规划入门详解 一 什么是动态规划&#xff1f;&#xff1f; 算法导论中介绍&#xff0c;动态规划和分治方法类似&#xff0c;都是听过子问题的解来解决原问题。下面说一下这2者之间的分别&#xff0c;分治方法将原问题划分为互不相交的子问题&#xff0c;而后将子问题组合…

【刷题日记】动态规划经典题目

&#x1f600;大家好&#xff0c;我是白晨&#xff0c;一个不是很能熬夜&#x1f62b;&#xff0c;但是也想日更的人✈。如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下&#x1f440;白晨吧&#xff01;你的支持就是我最大的动力&#xff01;&#x1f4…

Linux命令-sftp文件传输

搭建SFTP服务详见博文&#xff1a;https://blog.csdn.net/cen50958/article/details/90722874 连接SFTP 可使用&#xff1a;sftp --help 查看SFTP的连接参数 [rootstudy ~]# sftp --help usage: sftp [-1Cv] [-B buffer_size] [-b batchfile] [-F ssh_config] [-o ssh_option…

Linux命令(三):SFTP

目录 1、登录 2、文件上传 3、文件下载 4、删除文件/文件夹 5、实战 1、登录 sftp userip 你要用sftp, 当然得登录到sftp服务器&#xff0c; 在linux的shell中执行上面的命令后&#xff0c; linux shell会提示用户输入密码&#xff0c; 我们就输入password吧。 这样就成功…

Linux常用命令——sftp命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) sftp 交互式的文件传输程序 补充说明 sftp命令是一款交互式的文件传输程序&#xff0c;命令的运行和使用方式与ftp命令相似&#xff0c;但是&#xff0c;sftp命令对传输的所有信息使用ssh加密&#xff0c;它还…