背景
最近在做國產(chǎn)化中間件適配疏咐,替換Tomcat后供炼,發(fā)現(xiàn)原來上傳文件的功能失敗篙螟,原因是獲取不到上傳的文件信息我纪,最終通過跟蹤執(zhí)行流程悬赏,閱讀源碼树肃,發(fā)現(xiàn)了問題產(chǎn)生的原因蒸矛,也因此了解到了Springboot上傳文件的實(shí)現(xiàn)步驟
簡單說:tomcat 先將文件上傳到tomcat臨時目錄(我們一般都沒指定文件夾),tomcat 會將文件信息封裝為一個對象胸嘴,我們使用這個對象雏掠,就可以獲取文件流,從而將從頁面上傳的文件寫入到我們要上傳到的目標(biāo)文件夾下面
下面我們就一步步進(jìn)行反推劣像,從使用開始乡话!
1.文件上傳
這個不多說,在百度 搜索 springboot 上傳文件 就會有一大堆的demo可以參考
但不管代碼怎么變耳奕,最終都逃不過 MultipartHttpServletRequest 绑青,這個request對象
1.1上傳報錯屋群,MultipartFile 對象為空
更換中間件后闸婴,執(zhí)行上傳操作,發(fā)現(xiàn)獲取不到 圖1 所示sFileName
因此逐步排查芍躏,是什么原因?qū)е?sFileName 為空
2.排查
2.1 org.springframework.web.multipart.MultipartHttpServletRequest
這是一個接口
根據(jù)代碼邪乍,我們可以發(fā)現(xiàn),從傳進(jìn)來的HttpServletRequest會轉(zhuǎn)化為MultipartHttpServletRequest对竣,再從MultipartHttpServletRequest里獲取MultipartFile對象(也就是最終操作的文件對象)
2.2 org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest#getFiles
MultipartHttpServletRequest要獲取到MultipartFile對象庇楞,實(shí)際上是他的實(shí)現(xiàn)類AbstractMultipartHttpServletRequest去獲取
到這里的時候,multipartFiles 已經(jīng)被初始化了否纬!
那么他在哪里進(jìn)行初始化的呢吕晌?
2.3 org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#parseRequest
在這里
我們發(fā)現(xiàn), this.setMultipartFiles(files); 語句對 multipartFiles 進(jìn)行了賦值
但是賦值的值來源于 request.getParts();
那么临燃,問題來了
request的parts又是從哪來的呢睛驳?
2.4 org.springframework.web.servlet.DispatcherServlet#doDispatch
在這里會檢查是否是上傳請求,也就是在這里谬俄,對
StandardMultipartHttpServletRequest 進(jìn)行了初始化操作
在這里柏靶,還是沒解決 request的parts又是從哪來的呢?
繼續(xù)跟蹤
debug ...
3 debug 跟蹤
主要就是要找到溃论,是在什么時候屎蜓,對request的parts對象賦值了
第三章可以從后往前看,這是debug后倒推的步驟钥勋,看著會有點(diǎn)別扭
3.1 org.apache.catalina.connector.Request#parseParts
為什么突然定位到這里炬转,答案是debug過來的
這里有一個 MultipartConfigElement 對象在后面第四章講一下
3.1.1 tomcat進(jìn)行文件上傳
在這里辆苔,tomcat 會將文件上傳到 tomcat 的臨時目錄下,我們在后面讀取的文件流(第一章 圖1)扼劈,其實(shí)是讀取的 tomcat 放到服務(wù)器上的文件的文件流
我們可以指定 tomcat 將臨時文件放在哪里驻啤,只需要在配置文件里配置就行
比如:
spring.http.multipart.location=D:\\temp\\file\\upload
3.1.2 對parts賦值
這一步就是我們苦苦找尋的 request的parts又是從哪來的呢荐吵?
3.2 org.apache.catalina.connector.Request#parseParameters
從圖里我們也可以知道骑冗,為什么上傳圖片的時候要求 contentType = multipart/form-data 了
3.3 org.apache.catalina.connector.Request#getParameter
3.4 org.apache.catalina.connector.RequestFacade#getParameter
3.5 org.springframework.web.filter.HiddenHttpMethodFilter#doFilterInternal
post請求,會對參數(shù)做特殊處理先煎,這里面就涉及到了 tomcat 對文件的默認(rèn)上傳
4. MultipartConfigElement
我們從 圖 3.1-1 可以看到贼涩,有行代碼:
MultipartConfigElement mce = this.getWrapper().getMultipartConfigElement();
MultipartConfigElement 的作用是啥?
用來配置上傳文件大小等信息的
如果上傳的文件超過了默認(rèn)設(shè)置薯蝎,就會報下面的這個錯
Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (27503483) exceeds the configured maximum (10485760)
解決這個報錯的方法就是遥倦,在application.properties文件中加入下面這兩個配置就完了
# 單個文件大小
spring.http.multipart.maxFileSize=1000Mb
# 總上傳的數(shù)據(jù)大小
spring.http.multipart.maxRequestSize=10000Mb
4.1 MultipartProperties
Springboot又是在哪里讀取配置文件里的數(shù)據(jù)的?
直接在 MultipartConfigElement 的構(gòu)造器打上斷點(diǎn)占锯,你就可以跟蹤到 org.springframework.boot.autoconfigure.web.MultipartProperties
這個類有 ConfigurationProperties 注解袒哥,標(biāo)識是一個配置類,Springboot 會自動對他進(jìn)行賦值
5.總結(jié)
至此消略,從配置 文件上傳大小堡称,到文件上傳到我們指定的位置,就已經(jīng)非常清楚的走了一遍