欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談MultipartFile中transferTo方法的坑

 更新時(shí)間:2021年07月01日 10:32:58   作者:CrazyDragon_King  
這篇文章主要介紹了MultipartFile中transferTo方法的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

前言:最近用SpringBoot寫文件上傳功能,使用參數(shù)綁定之后確實(shí)是非常的方便了。

但是,項(xiàng)目部署就出現(xiàn)了問題,搞得我一臉懵逼。

后來(lái),才發(fā)現(xiàn)是因?yàn)槲沂褂昧讼鄬?duì)路徑導(dǎo)致的,這個(gè)絕對(duì)是一個(gè)坑人的地方,不過也說明需要學(xué)習(xí)的東西還有很多!

案例再現(xiàn)

@PostMapping("/uploadFile")
public String uploadImg(@RequestParam("file") MultipartFile file, @RequestParam("equipmentId") String equipmentId) {
String baseDir = "./imgFile";  // 這里不能直接使用相對(duì)路徑
  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);
          // 第一次執(zhí)行代碼時(shí),路徑是不存在的
          logger.info("文件保存路徑:{},是否存在:{}", filePath.getParentFile().exists(), filePath.getParent());
          if (!filePath.getParentFile().exists()) {   // 如果存放路徑的父目錄不存在,就創(chuàng)建它。
              filePath.getParentFile().mkdirs();
          }
          // 如果路徑不存在,上面的代碼會(huì)創(chuàng)建路徑,此時(shí)路徑即已經(jīng)創(chuàng)建好了
          logger.info("文件保存路徑:{},是否存在:{}", filePath.getParentFile().exists(), filePath.getParent());
          // 此處使用相對(duì)路徑,似乎是一個(gè)坑!
          // 相對(duì)路徑:filePath
          // 絕對(duì)路徑:filePath.getAbsoluteFile()
          logger.info("文件將要保存的路徑:{}", filePath.getPath());
          file.transferTo(filePath);
          logger.info("文件成功保存的路徑:{}", filePath.getAbsolutePath());
          return "上傳成功";
      } catch (Exception e) {
          logger.error(e.getMessage());
      }
  }
  return "上傳失敗";
}

我在日志中打印了路徑的位置,顯示是沒有問題,當(dāng)時(shí)一旦執(zhí)行到file.transferTo(filePath);就會(huì)產(chǎn)生一個(gè)FileNotFoundException,但是我前面的代碼是執(zhí)行了,并且創(chuàng)建了一個(gè)文件夾的。

Postman測(cè)試截圖


在這里插入圖片描述

日志輸出

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 (系統(tǒng)找不到指定的路徑。)

注意: 這里雖然沒有什么頭緒,當(dāng)時(shí)觀察日志可以發(fā)現(xiàn),程序試圖將文件保存到一個(gè)很奇怪的目錄下,當(dāng)是這個(gè)目錄和前面那個(gè)filePath已經(jīng)沒有關(guān)系了,這里是一個(gè)疑點(diǎn)!

執(zhí)行之后代碼所在目錄下面已經(jīng)創(chuàng)建了一個(gè)imgFile目錄

在這里插入圖片描述

imgFile文件夾中是空的,因?yàn)閳?zhí)行transferTo時(shí)拋出了異常

在這里插入圖片描述

修改此處傳如的參數(shù),改為文件的絕對(duì)路徑

file.transferTo(filePath.getAbsoluteFile());

Postman測(cè)試截圖

上傳成功!

在這里插入圖片描述

執(zhí)行之后代碼所在目錄下面已經(jīng)創(chuàng)建了一個(gè)imgFile目錄

在這里插入圖片描述

imgFile文件夾中已經(jīng)有了上傳的圖片

在這里插入圖片描述

原因分析

上面失敗與成功只是因?yàn)槁窂剿淼氖窍鄬?duì)路徑和絕對(duì)路徑的區(qū)別。這就說明是MultiparFile的transferTo方法有問題了。讓我們加一個(gè)斷點(diǎn),調(diào)試走一波!debug!

補(bǔ)充一個(gè)debug的小知識(shí):

debug tips:
step into: 單步執(zhí)行,遇到子函數(shù)就進(jìn)入并且繼續(xù)單步執(zhí)行(F5)
step over: 在單步執(zhí)行時(shí),在函數(shù)內(nèi)遇到子函數(shù)時(shí)不會(huì)進(jìn)入子函數(shù)內(nèi)單步執(zhí)行,而是將子函數(shù)整個(gè)執(zhí)行完再停止,也就是把子函數(shù)整個(gè)作為一步(F6)
step return: 在單步執(zhí)行到子函數(shù)內(nèi)時(shí),用step return就可以執(zhí)行完子函數(shù)余下部分,并返回上一層。
setp out: 效果同 step return。

我這里只給file.transferTo(filePath.getAbsoluteFile());這行代碼加了斷點(diǎn),這里我給出調(diào)試中最重要的兩個(gè)步驟:

調(diào)試中代碼的執(zhí)行流程是:

但代碼進(jìn)入 transferTo 后,然后執(zhí)行 this.part.write(dest.getpath)方法,進(jìn)入 write 方法內(nèi)部,到這里就可以得到我們的答案了!

@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);
	}
}

這個(gè)write方法,會(huì)判斷傳入的參數(shù)是否是相對(duì)路徑,如果是相對(duì)路徑,它會(huì)自己給我們拼接一個(gè)父路徑! 所以你應(yīng)該知道那個(gè)奇怪的路徑是哪里來(lái)的了吧!

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

好了,大概可以理清了,這是因?yàn)閠ransferTo的參數(shù),如果是相對(duì)路徑的話,程序會(huì)自己拼接一個(gè)父路徑,因?yàn)槲抑付ǖ南鄬?duì)路徑中帶有一個(gè)不存在的路徑,如果嘗試保存是會(huì)失敗的。但是如果你傳入的參數(shù)只是一個(gè)文件名,那應(yīng)該就能保存成功。但是這樣,取文件的時(shí)候,又會(huì)遇到問題了,你可能都不知道文件在哪里!

補(bǔ)充 一下吧

這里還有一個(gè)很有意思的地方,如果我的相對(duì)路徑中不使用 . 開頭,而只是以 / 開頭,那么又會(huì)產(chǎn)生一個(gè)好玩的情況了。第一種情況就算剛才那樣的,這里我們來(lái)討論第二種情況,這種情況在Windows系統(tǒng)中還是同第一種一樣的錯(cuò)誤,但是在Linux系統(tǒng)中,它是可以正常執(zhí)行的。如果你了解一點(diǎn)兩個(gè)系統(tǒng)的知識(shí)的話,就應(yīng)該知道Linux系統(tǒng)的根路徑就是 /,所以以 / 開頭的路徑即是絕對(duì)路徑。

所以這也算是程序跨平臺(tái)需要考慮的問題了,如果不了解Linux的話,你可能不會(huì)明白,這里我給出一個(gè)驗(yàn)證程序?qū)嶋H測(cè)試一下。

Windows系統(tǒng)和Linux系統(tǒng)運(yùn)行結(jié)果不同的代碼。

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是絕對(duì)路徑嗎? " + file1.isAbsolute());
		System.out.println("file2: " + file1 + " file2是絕對(duì)路徑嗎? " + file2.isAbsolute());
		try {
			System.out.println(file1.getCanonicalPath());
			System.out.println(file2.getCanonicalPath());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}	
}

Windows運(yùn)行結(jié)果

在這里插入圖片描述

Linux運(yùn)行結(jié)果

這里需要一個(gè)Linux環(huán)境,但是我的電腦上面沒有,雖然我買了一臺(tái)阿里云服務(wù)器。但是為了這么小小的一段代碼登陸阿里云服務(wù)器去執(zhí)行,我又嫌麻煩。還好我想到了一個(gè)更加巧妙的方法!

以前,知乎上面曾經(jīng)有一個(gè)問題是關(guān)于菜鳥教程的,然后菜鳥教程的作者親自出來(lái)回答了問題,并且貼了一張圖片——菜鳥教程技術(shù)結(jié)構(gòu)圖譜

這個(gè)圖片本身其實(shí)是涉及到了很多的,但是我們這里只關(guān)注一個(gè)就是在線代碼提交執(zhí)行,看到那只可愛的鯨魚了嗎?對(duì),它就是docker。Docker里面就是一個(gè)完整的操作系統(tǒng),并且是Linux系統(tǒng)!

好了,打開 菜鳥教程–>java教程–>隨便找一個(gè)運(yùn)行實(shí)例,進(jìn)去刪除原來(lái)的代碼,復(fù)制我這個(gè)代碼上去執(zhí)行,輸出結(jié)果!嘿嘿

在這里插入圖片描述

注意:

有些在線代碼執(zhí)行是屏蔽了某些包的,所以有的也不一定是可以執(zhí)行成功的,如果這里作者對(duì)在線代碼提交執(zhí)行做了那種限制,我們還是只能老老實(shí)實(shí)的去Linux系統(tǒng)上面執(zhí)行了。

不過,有時(shí)候站在巨人的肩膀上,真的是挺輕松的!

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Bean實(shí)例化之前修改BeanDefinition示例詳解

    Bean實(shí)例化之前修改BeanDefinition示例詳解

    這篇文章主要為大家介紹了Bean實(shí)例化之前修改BeanDefinition示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • 使用MyBatis從hive中讀取數(shù)據(jù)

    使用MyBatis從hive中讀取數(shù)據(jù)

    Hive是一個(gè)基于Hadoop的數(shù)據(jù)倉(cāng)庫(kù)工具,它可以方便地對(duì)大規(guī)模數(shù)據(jù)進(jìn)行查詢和分析,本文主要介紹了使用MyBatis從hive中讀取數(shù)據(jù),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-05-05
  • 簡(jiǎn)單了解JAVA中類、實(shí)例與Class對(duì)象

    簡(jiǎn)單了解JAVA中類、實(shí)例與Class對(duì)象

    這篇文章主要介紹了簡(jiǎn)單了解JAVA中類、實(shí)例與Class對(duì)象,類是面向?qū)ο缶幊陶Z(yǔ)言的一個(gè)重要概念,它是對(duì)一項(xiàng)事物的抽象概括,可以包含該事物的一些屬性定義,以及操作屬性的方法,需要的朋友可以參考下
    2019-06-06
  • Spring注解驅(qū)動(dòng)之@EventListener注解使用方式

    Spring注解驅(qū)動(dòng)之@EventListener注解使用方式

    這篇文章主要介紹了Spring注解驅(qū)動(dòng)之@EventListener注解使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • 解決mybatis-plus使用jdk8的LocalDateTime 查詢時(shí)報(bào)錯(cuò)的方法

    解決mybatis-plus使用jdk8的LocalDateTime 查詢時(shí)報(bào)錯(cuò)的方法

    這篇文章主要介紹了解決mybatis-plus使用jdk8的LocalDateTime 查詢時(shí)報(bào)錯(cuò)的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 從lombok的val和var到JDK的var關(guān)鍵字方式

    從lombok的val和var到JDK的var關(guān)鍵字方式

    這篇文章主要介紹了從lombok的val和var到JDK的var關(guān)鍵字方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Java隱藏特性之雙括號(hào)初始化詳解

    Java隱藏特性之雙括號(hào)初始化詳解

    Java?語(yǔ)言擁有許多隱藏而強(qiáng)大的特性,其中之一是雙括號(hào)初始化,這篇文章將詳細(xì)介紹雙括號(hào)初始化的概念、用法和示例代碼,希望對(duì)大家有所幫助
    2023-12-12
  • Struts2中ognl遍歷數(shù)組,list和map方法詳解

    Struts2中ognl遍歷數(shù)組,list和map方法詳解

    這篇文章主要介紹了Struts2中ognl遍歷數(shù)組,list和map方法詳解,需要的朋友可以參考下。
    2017-09-09
  • 快速排序算法原理及java遞歸實(shí)現(xiàn)

    快速排序算法原理及java遞歸實(shí)現(xiàn)

    快速排序 對(duì)冒泡排序的一種改進(jìn),若初始記錄序列按關(guān)鍵字有序或基本有序,蛻化為冒泡排序。使用的是遞歸原理,在所有同數(shù)量級(jí)O(n longn) 的排序方法中,其平均性能最好。就平均時(shí)間而言,是目前被認(rèn)為最好的一種內(nèi)部排序方法
    2014-01-01
  • SpringBoot中快速實(shí)現(xiàn)郵箱發(fā)送代碼解析

    SpringBoot中快速實(shí)現(xiàn)郵箱發(fā)送代碼解析

    這篇文章主要介紹了SpringBoot中快速實(shí)現(xiàn)郵箱發(fā)送代碼解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08

最新評(píng)論