SpringBoot文件上傳的原理解析
一、請(qǐng)求進(jìn)入,使用文件上傳解析器判斷并封裝
文件上傳解析器:只能接收標(biāo)準(zhǔn)的 Servlet 方式上傳的文件
@ConditionalOnMissingBean({MultipartResolver.class})
判斷容器中無(wú)文件上傳解析器,若無(wú)自動(dòng)創(chuàng)建
@ConditionalOnMissingBean({MultipartResolver.class}) // 判斷注解 public StandardServletMultipartResolver multipartResolver() { StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver(); multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily()); return multipartResolver; }
DispatcherServlet.class —— doDispatch()方法
與文件上傳相關(guān)功能有關(guān)的語(yǔ)句,請(qǐng)見下面的注釋:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; // 判斷是否是文件上傳請(qǐng)求(封裝好的文件上傳請(qǐng)求與原請(qǐng)求不相等,則判斷是文件上傳請(qǐng)求) mappedHandler = this.getHandler(processedRequest); // 找誰(shuí)能處理文件上傳請(qǐng)求 if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } } }
(1) processedRequest = this.checkMultipart(request)
—— 用于判斷是否是文件上傳請(qǐng)求
Step Into 查看:發(fā)現(xiàn)使用 multipartResolver
判斷是否是文件上傳請(qǐng)求
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { // 判斷是否是文件上傳請(qǐng)求 if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { if (request.getDispatcherType().equals(DispatcherType.REQUEST)) { this.logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter"); } } else if (this.hasMultipartException(request)) { this.logger.debug("Multipart resolution previously failed for current request - skipping re-resolution for undisturbed error rendering"); } else { try { return this.multipartResolver.resolveMultipart(request); // 解析文件上傳請(qǐng)求 } catch (MultipartException var3) { if (request.getAttribute("javax.servlet.error.exception") == null) { throw var3; } } this.logger.debug("Multipart resolution failed for error dispatch", var3); } } return request; }
StepInto—— multipartResolver
判斷是否是文件上傳請(qǐng)求方式
用 String 工具類判斷是否以 multipart/ 開頭(這也解釋了為什么我們?cè)趯懬岸吮韱谓邮瘴募r(shí),必須使用multipart)
public boolean isMultipart(HttpServletRequest request) { return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/"); }
(2) this.multipartResolver.resolveMultipart(request)
解析文件上傳請(qǐng)求
其將文件上傳請(qǐng)求封裝為 MultipartHttpServletRequest
類返回
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException { return new StandardMultipartHttpServletRequest(request, this.resolveLazily); }
二、參數(shù)解析器解析請(qǐng)求中的文件內(nèi)容封裝成 MultipartFile
文件請(qǐng)求參數(shù)解析器:
(1) InvocableHandlerMethod.class
找到參數(shù)解析器,執(zhí)行文件上傳代理
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest servletRequest = (HttpServletRequest)request.getNativeRequest(HttpServletRequest.class); Assert.state(servletRequest != null, "No HttpServletRequest"); RequestPart requestPart = (RequestPart)parameter.getParameterAnnotation(RequestPart.class); boolean isRequired = (requestPart == null || requestPart.required()) && !parameter.isOptional(); String name = this.getPartName(parameter, requestPart); parameter = parameter.nestedIfOptional(); Object arg = null; Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); // 文件上傳代理 if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { arg = mpArg; } else { try { HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name); arg = this.readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType()); if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(request, arg, name); if (arg != null) { this.validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } if (mavContainer != null) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); } } } catch (MultipartException | MissingServletRequestPartException var13) { if (isRequired) { throw var13; } } } }
(2)確定每個(gè)參數(shù)的值
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
(3)解析所有參數(shù)
for(int i = 0; i < parameters.length; ++i) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] == null) { if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception var10) { if (logger.isDebugEnabled()) { String exMsg = var10.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw var10; } } }
三、將 request 文件封裝為一個(gè) Map (Map<String, MultipartFile>)
AbstractMultipartHttpServletRequest.class
public List<MultipartFile> getFiles(String name) { List<MultipartFile> multipartFiles = (List)this.getMultipartFiles().get(name); return multipartFiles != null ? multipartFiles : Collections.emptyList(); }
public Map<String, MultipartFile> getFileMap() { return this.getMultipartFiles().toSingleValueMap(); }
總結(jié):
- 請(qǐng)求進(jìn)入,使用文件上傳解析器判斷(isMultipart)并封裝(resolveMultipart,返回MultipartHttpServletRequest)文件上傳請(qǐng)求
- 參數(shù)解析器解析請(qǐng)求中的文件內(nèi)容封裝成 MultipartFile
- 將 request 文件封裝為一個(gè) Map (Map<String, MultipartFile>)
通過(guò)觀察源碼,可以得到許多 SpringBoot 為我們封裝好的文件工具類,如 FileCopyUtils 實(shí)現(xiàn)文件流的拷貝
到此這篇關(guān)于SpringBoot文件上傳的原理解析的文章就介紹到這了,更多相關(guān)SpringBoot文件上傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot+ruoyi框架文件上傳和下載的實(shí)現(xiàn)
- tdesign的文件上傳功能實(shí)現(xiàn)(微信小程序+idea的springboot)
- SpringBoot中的文件上傳和異常處理詳解
- SpringBoot中的文件上傳與下載詳解
- Springboot文件上傳功能的實(shí)現(xiàn)
- SpringBoot文件上傳同時(shí)接收復(fù)雜參數(shù)的過(guò)程詳解
- SpringBoot實(shí)現(xiàn)項(xiàng)目文件上傳的方法詳解
- SpringBoot文件上傳與下載功能實(shí)現(xiàn)詳解
- SpringBoot簡(jiǎn)單實(shí)現(xiàn)文件上傳
- SpringBoot整合Hutool實(shí)現(xiàn)文件上傳的使用示例
相關(guān)文章
Spring?Boot項(xiàng)目如何使用Maven打包并帶上依賴
在這篇博客中,介紹如何使用Maven將Spring?Boot項(xiàng)目及其依賴項(xiàng)打包成一個(gè)可執(zhí)行的jar文件。我們將使用Spring?Boot的spring-boot-maven-plugin插件來(lái)完成這個(gè)任務(wù),感興趣的朋友跟隨小編一起看看吧2023-06-06springcloud集成nacos?使用lb?無(wú)效問(wèn)題解決方案
這篇文章主要介紹了解決springcloud集成nacos?使用lb?無(wú)效,通過(guò)查看spring-cloud-starter-gateway?jar中的自動(dòng)配置類的源碼,得知,該jar包中是不支持負(fù)載均衡的,需要引入spring-cloud-starter-loadbalancer?來(lái)支持,需要的朋友可以參考下2023-04-04java?NIO實(shí)現(xiàn)簡(jiǎn)單聊天程序
這篇文章主要為大家詳細(xì)介紹了java?NIO實(shí)現(xiàn)簡(jiǎn)單聊天程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11Java發(fā)送郵箱驗(yàn)證碼、session校驗(yàn)功能
本篇主要描述“發(fā)送郵箱驗(yàn)證碼、session校驗(yàn)”相關(guān)前(html\js)后(java)臺(tái)代碼,業(yè)務(wù)邏輯示例,需要的朋友可以參考下2018-02-02SpringBoot聲明式事務(wù)的簡(jiǎn)單運(yùn)用說(shuō)明
這篇文章主要介紹了SpringBoot聲明式事務(wù)的簡(jiǎn)單運(yùn)用說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09Gson中@JsonAdater注解的幾種方式總結(jié)
這篇文章主要介紹了Gson中@JsonAdater注解的幾種方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08