浅谈MultipartFile中transferTo⽅法的坑
前⾔:最近⽤SpringBoot写⽂件上传功能,使⽤参数绑定之后确实是⾮常的⽅便了。
但是,项⽬部署就出现了问题,搞得我⼀脸懵逼。
后来,才发现是因为我使⽤了相对路径导致的,这个绝对是⼀个坑⼈的地⽅,不过也说明需要学习的东西还有很多!
案例再现
@PostMapping("/uploadFile")
public String uploadImg(@RequestParam("file") MultipartFile file, @RequestParam("equipmentId") String equipmentId) {
String baseDir = "./imgFile"; // 这⾥不能直接使⽤相对路径
if (!file.isEmpty()) {
String name = OriginalFilename();
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("⽂件保存路径:{},是否存在:{}", ParentFile().exists(), Parent());
if (!ParentFile().exists()) { // 如果存放路径的⽗⽬录不存在,就创建它。
}
// 如果路径不存在,上⾯的代码会创建路径,此时路径即已经创建好了
logger.info("⽂件保存路径:{},是否存在:{}", ParentFile().exists(), Parent());
// 此处使⽤相对路径,似乎是⼀个坑!
// 相对路径:filePath
// 绝对路径:AbsoluteFile()
logger.info("⽂件将要保存的路径:{}", Path());
logger.info("⽂件成功保存的路径:{}", AbsolutePath());
return "上传成功";
} catch (Exception e) {
<(e.getMessage());
}
}
return "上传失败";
}
我在⽇志中打印了路径的位置,显⽰是没有问题,当时⼀旦执⾏到ansferTo(filePath);就会产⽣⼀个FileNotFoundException,但是我前⾯的代码是执⾏了,并且创建了⼀个⽂件夹的。
Postman测试截图
⽇志输出
mkdirs方法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时抛出了异常
修改此处传如的参数,改为⽂件的绝对路径
Postman测试截图
上传成功!
执⾏之后代码所在⽬录下⾯已经创建了⼀个imgFile⽬录
imgFile⽂件夹中已经有了上传的图⽚
原因分析
上⾯失败与成功只是因为路径所代表的是相对路径和绝对路径的区别。这就说明是MultiparFile的transferTo⽅法有问题了。让我们加⼀个断点,调试⾛⼀波!debug!补充⼀个debug的⼩知识:
debug tips:
step into: 单步执⾏,遇到⼦函数就进⼊并且继续单步执⾏(F5)
step over: 在单步执⾏时,在函数内遇到⼦函数时不会进⼊⼦函数内单步执⾏,⽽是将⼦函数整个执⾏
完再停⽌,也就是把⼦函数整个作为⼀步(F6)step return: 在单步执⾏到⼦函数内时,⽤step return就可以执⾏完⼦函数余下部分,并返回上⼀层。
setp out: 效果同 step return。
我这⾥只给AbsoluteFile());这⾏代码加了断点,这⾥我给出调试中最重要的两个步骤:
调试中代码的执⾏流程是:
但代码进⼊ transferTo 后,然后执⾏ this.part.path)⽅法,进⼊ write ⽅法内部,到这⾥就可以得到我们的答案了!
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
this.part.Path());
if (dest.isAbsolute() && !ists()) {
/
/ 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.
}
}
@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.CanonicalPath());
System.out.CanonicalPath());
} catch (IOException e) {
e.printStackTrace();
}
}
}
Windows运⾏结果
Linux运⾏结果
这⾥需要⼀个Linux环境,但是我的电脑上⾯没有,虽然我买了⼀台阿⾥云服务器。但是为了这么⼩⼩的⼀段代码登陆阿⾥云服务器去执⾏,我⼜嫌⿇烦。还好我想到了⼀个更加巧妙的⽅法!
以前,知乎上⾯曾经有⼀个问题是关于菜鸟教程的,然后菜鸟教程的作者亲⾃出来回答了问题,并且贴了⼀张图⽚——菜鸟教程技术结构图谱
这个图⽚本⾝其实是涉及到了很多的,但是我们这⾥只关注⼀个就是在线代码提交执⾏,看到那只可爱的鲸鱼了吗?对,它就是docker。Docker⾥⾯就是⼀个完整的操作系统,并且是Linux系统!
好了,打开菜鸟教程–>java教程–>随便⼀个运⾏实例,进去删除原来的代码,复制我这个代码上去执⾏,输出结果!嘿嘿
注意:
有些在线代码执⾏是屏蔽了某些包的,所以有的也不⼀定是可以执⾏成功的,如果这⾥作者对在线代码提交执⾏做了那种限制,我们还是只能⽼⽼实实的去Linux系统上⾯执⾏了。
不过,有时候站在巨⼈的肩膀上,真的是挺轻松的!
以上为个⼈经验,希望能给⼤家⼀个参考,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论