SpringMVC文件上傳請(qǐng)求問(wèn)題分析
我們文件上傳接口只需要在方法參數(shù)上寫(xiě)MultipartFile類(lèi),mvc就可以幫我們把上傳的文件封裝為這個(gè)類(lèi)的對(duì)
象供我們非常方便的操作,那它是怎么做的呢?我們一起來(lái)看看
我們發(fā)的請(qǐng)求默認(rèn)都是由DispatcherServlet類(lèi)的doDispatch()
來(lái)處理,這個(gè)方法的邏輯處理的第一步就是處理文件上傳的請(qǐng)求,我們一起來(lái)看看是怎么處理的吧。
本文分析的問(wèn)題:文件上傳請(qǐng)求的執(zhí)行原理、文件上傳自動(dòng)配置原理
執(zhí)行流程原理
checkMultipart()-處理文件上傳的請(qǐng)求
processedRequest = checkMultipart(request)
:處理文件上傳請(qǐng)求。所以我們把這個(gè)方法看明白就知道了
@Nullable // 文件上傳解析器,只能有一個(gè) private MultipartResolver multipartResolver; protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { // 1.利用文件上傳解析器來(lái)判斷是否是文件上傳請(qǐng)求 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { // 如果之前被MultipartFilter包裝過(guò)了,就不做處理 if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { if (DispatcherType.REQUEST.equals(request.getDispatcherType())) { logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter"); } } // 是否有異常 else if (hasMultipartException(request)) { logger.debug("Multipart resolution previously failed for current request - " + "skipping re-resolution for undisturbed error rendering"); } else { try { // 2、利用文件上傳解析器來(lái)解析文件上傳的請(qǐng)求 return this.multipartResolver.resolveMultipart(request); } catch (MultipartException ex) { if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) { logger.debug("Multipart resolution failed for error dispatch", ex); // Keep processing error dispatch with regular request handle below } else { throw ex; } } } } // If not returned before: return original request. return request; }
流程:
- 利用 MultipartResolver(文件上傳解析器)來(lái)判斷是否是文件上傳請(qǐng)求:
isMultipart
默認(rèn)使用StandardServletMultipartResolver
- 利用 MultipartResolver(文件上傳解析器)來(lái)解析文件上傳的請(qǐng)求:
resolveMultipart()
會(huì)把請(qǐng)求包裝為StandardMultipartHttpServletRequest
這些工作都是利用文件上傳解析器來(lái)做的,所以我們把文件上傳解析器搞明白也就知道了
繼承圖:
MultipartResolver(文件上傳解析器)
主要作用就是把真實(shí)文件包裝為MultipartFile對(duì)象,并緩存起來(lái)以便后面封裝參數(shù)時(shí)使用
public interface MultipartResolver { // 判斷請(qǐng)求是否是文件上傳的請(qǐng)求 boolean isMultipart(HttpServletRequest request); // 將請(qǐng)求包裝為 StandardMultipartHttpServletRequest MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException; // 清除資源 void cleanupMultipart(MultipartHttpServletRequest request); }
1、 isMultipart()
:判斷請(qǐng)求是否是文件上傳的請(qǐng)求。其實(shí)就是判斷 Content-Type 的值是否是以
multipart/form-data 或 multipart/ 開(kāi)頭
(這里也就解釋了為啥我們發(fā)送文件上傳的請(qǐng)求時(shí) Content-Type的值要為 multipart/form-data)
2、resolveMultipart()
:將請(qǐng)求包裝為MultipartHttpServletRequest
MultipartResolver 的默認(rèn)實(shí)現(xiàn)是 StandardServletMultipartResolver,它會(huì)把請(qǐng)求封裝為StandardMultipartHttpServletRequest,把文件封裝為StandardMultipartFile
// 是否延遲解析 private boolean resolveLazily = false; // 判斷 public boolean isMultipart(HttpServletRequest request) { return StringUtils.startsWithIgnoreCase(request.getContentType(), (this.strictServletCompliance ? MediaType.MULTIPART_FORM_DATA_VALUE : "multipart/")); } // 包裝請(qǐng)求 public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException { return new StandardMultipartHttpServletRequest(request, this.resolveLazily); }
MultipartHttpServletRequest(文件上傳請(qǐng)求)
默認(rèn)實(shí)現(xiàn)StandardMultipartHttpServletRequest
,會(huì)把文件封裝為StandardMultipartFile
// 創(chuàng)建文件上傳請(qǐng)求 public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException { super(request); // 是否延遲解析,默認(rèn)false if (!lazyParsing) { // 解析請(qǐng)求 parseRequest(request); } } private void parseRequest(HttpServletRequest request) { try { // 從 HttpServletRequest 中獲取上傳的文件 Collection<Part> parts = request.getParts(); this.multipartParameterNames = new LinkedHashSet<>(parts.size()); // 存儲(chǔ)封裝好的文件對(duì)象 MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size()); // 遍歷所有的文件 for (Part part : parts) { String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION); ContentDisposition disposition = ContentDisposition.parse(headerValue); // 獲取文件名字 String filename = disposition.getFilename(); if (filename != null) { // 添加到集合中 files.add(part.getName(), new StandardMultipartFile(part, filename)); } else { // 沒(méi)有文件名,就是普通參數(shù)了 this.multipartParameterNames.add(part.getName()); } } // 將上面所有生成的 StandardMultipartFile 文件對(duì)象設(shè)置到父類(lèi)的 multipartFiles屬性中 // 以便后面封裝參數(shù)時(shí)使用 setMultipartFiles(files); } catch (Throwable ex) { handleParseFailure(ex); } } protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) { this.multipartFiles = new LinkedMultiValueMap<>(Collections.unmodifiableMap(multipartFiles)); }
從
HttpServletRequest
中獲取上傳的文件Part
遍歷所有的文件
從
Part
中獲取請(qǐng)求頭Content-Disposition
的值,解析生成ContentDisposition
對(duì)象,然后獲取文件名情況1:文件名不為空,說(shuō)明是文件,把文件封裝為
StandardMultipartFile
對(duì)象情況2:文件名為空,說(shuō)明是普通參數(shù),則保存參數(shù)名稱(chēng)
將上面所有生成的
StandardMultipartFile
文件對(duì)象設(shè)置到父類(lèi)的multipartFiles
屬性中,以便后面封裝參數(shù)時(shí)使用
整個(gè)執(zhí)行的原理到這里也就完畢了。
自動(dòng)配置原理
文件上傳的自動(dòng)配置類(lèi)是MultipartAutoConfiguration
@AutoConfiguration @ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class }) @ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(MultipartProperties.class) public class MultipartAutoConfiguration { private final MultipartProperties multipartProperties; public MultipartAutoConfiguration(MultipartProperties multipartProperties) { this.multipartProperties = multipartProperties; } @Bean @ConditionalOnMissingBean(MultipartConfigElement.class) public MultipartConfigElement multipartConfigElement() { return this.multipartProperties.createMultipartConfig(); } @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) @ConditionalOnMissingBean(MultipartResolver.class) public StandardServletMultipartResolver multipartResolver() { StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver(); multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily()); return multipartResolver; } }
啟用文件上傳的配置類(lèi)
MultipartProperties
,配置前綴:spring.servlet.multipart
也就是說(shuō)我們可以通過(guò)這個(gè)類(lèi),然后在
application.yml
配置文件中來(lái)配置默認(rèn)底層的規(guī)則給容器中導(dǎo)入了
MultipartConfigElement
類(lèi):文件上傳的配置我們可以在容器中自己注冊(cè)這個(gè)類(lèi)
給容器中導(dǎo)入了文件上傳解析器
StandardServletMultipartResolver
,標(biāo)準(zhǔn)的Servlet文件上傳解析器,用來(lái)處理文件上傳設(shè)置了
resolveLazily
屬性:解析文件是否延遲解析,默認(rèn)不是延遲解析
我們也可以往容器中注冊(cè)我們自定義的文件上傳解析器,SpringBoot就會(huì)使用我們的。因?yàn)橛袟l件注解來(lái)動(dòng)態(tài)判斷
總結(jié)
執(zhí)行流程:利用 StandardServletMultipartResolver
(文件上傳解析器)來(lái)包裝請(qǐng)求、把每一個(gè)真實(shí)文件封裝為MultipartFile
類(lèi),以便我們能簡(jiǎn)單的操作。還會(huì)把所有的MultipartFile
對(duì)象設(shè)置到父類(lèi)的multipartFiles
屬性中,以便后面封裝參數(shù)時(shí)使用
自動(dòng)配置:利用SpringBoot的自動(dòng)配置往容器中注冊(cè)一個(gè)默認(rèn)的文件上傳解析器
注意:文件上傳解析器默認(rèn)全局只能有一個(gè),不能像 HandlerMapping
、HandlerAdapter
有多個(gè)
DispatcherServlet中就是這么寫(xiě)的
@Nullable private MultipartResolver multipartResolver; private List<HandlerMapping> handlerMappings; /** List of HandlerAdapters used by this servlet. */ @Nullable private List<HandlerAdapter> handlerAdapters;
到此這篇關(guān)于SpringMVC文件上傳請(qǐng)求的文章就介紹到這了,更多相關(guān)SpringMVC文件上傳請(qǐng)求內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java編程中拷貝數(shù)組的方式及相關(guān)問(wèn)題分析
這篇文章主要介紹了java編程中拷貝數(shù)組的方式及相關(guān)問(wèn)題分析,分享了Java中數(shù)組復(fù)制的四種方式,其次對(duì)二維數(shù)組的簡(jiǎn)單使用有一段代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11Java通過(guò)URL類(lèi)下載圖片的實(shí)例代碼
這篇文章主要介紹了Java通過(guò)URL類(lèi)下載圖片,文中結(jié)合實(shí)例代碼補(bǔ)充介紹了java通過(guò)url獲取圖片文件的相關(guān)知識(shí),代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02easyexcel讀取excel合并單元格數(shù)據(jù)的操作代碼
這篇文章主要介紹了easyexcel讀取excel合并單元格數(shù)據(jù)的操作代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05通俗易懂的Java常見(jiàn)限流算法具體實(shí)現(xiàn)
這篇文章主要介紹了Java常見(jiàn)限流算法具體實(shí)現(xiàn)的相關(guān)資料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的實(shí)現(xiàn)原理和具體步驟,并比較了它們的優(yōu)點(diǎn)和缺點(diǎn),需要的朋友可以參考下2025-02-02Spring Web項(xiàng)目spring配置文件隨服務(wù)器啟動(dòng)時(shí)自動(dòng)加載
這篇文章主要介紹了Spring Web項(xiàng)目spring配置文件隨服務(wù)器啟動(dòng)時(shí)自動(dòng)加載,加載spring的配置文件,并且只加載一次,從而提高程序效率。具體內(nèi)容詳情大家通過(guò)本文一起學(xué)習(xí)吧2018-01-01java后端+前端使用WebSocket實(shí)現(xiàn)消息推送的詳細(xì)流程
后端向前端推送消息就需要長(zhǎng)連接,首先想到的就是websocket,下面這篇文章主要給大家介紹了關(guān)于java后端+前端使用WebSocket實(shí)現(xiàn)消息推送的詳細(xì)流程,需要的朋友可以參考下2022-10-10Spring boot中自定義Json參數(shù)解析器的方法
這篇文章主要介紹了Spring boot中自定義Json參數(shù)解析器的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01java遍歷http請(qǐng)求request的所有參數(shù)實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇java遍歷http請(qǐng)求request的所有參數(shù)實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-09-09Java 反射獲取類(lèi)詳細(xì)信息的常用方法總結(jié)
Java 反射獲取類(lèi)詳細(xì)信息的常用方法總結(jié),需要的朋友可以參考一下2013-03-03