最近在項(xiàng)目中,使用sPRingmvc 進(jìn)行上傳文件時(shí),出現(xiàn)了一個(gè)問題:
org.springframework.web.multipart.MultipartException: The current request is not a multipart request
....
以上堆棧信息省略。
乍看一下,沒啥值得討論的地方,就是說當(dāng)前這個(gè)請(qǐng)求不是一個(gè)multipart request,也就是說不是上傳文件的請(qǐng)求。但是,這結(jié)果還是令我稍感意外,為什么呢?因?yàn)椋冶疽馐菍⑽募@個(gè)參數(shù)作為非必要參數(shù),類似下面這樣:
@RequestMapping(value = "/upload", method = RequestMethod.POST)public ResultView upload(@RequestParam(value = "file", required = false) MultipartFile file)spring拋出上面的異常,就違背了我的本意,我明明設(shè)置了 “required = false”, 為什么還是不行? 于是,帶著疑問去看了一下spring的源碼,下面就跟大家分享一下spring mvc對(duì)于文件上傳的處理。
--------------------------------------------------我是華麗的分割線-------------------------------------------------------
在spring mvc通過DispatcherServlet處理請(qǐng)求時(shí),會(huì)調(diào)用到 doDispatch這個(gè)方法,當(dāng)然這也是spring mvc處理請(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 { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request);上面就是給出的有關(guān)上傳文件的代碼片段,看以看到,當(dāng)spring處理請(qǐng)求的時(shí)候,首先第一步就去檢查當(dāng)前請(qǐng)求是否為上傳文件的請(qǐng)求,那么,它是怎么檢查的呢,接著往下看:
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " + "this typically results from an additional MultipartFilter in web.xml"); } else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) { logger.debug("Multipart resolution failed for current request before - " + "skipping re-resolution for undisturbed error rendering"); } else { return this.multipartResolver.resolveMultipart(request); } } // If not returned before: return original request. return request; }通過以上方法,我們可以看到如下邏輯:
(1)當(dāng) MultipartResolver 不為null的時(shí)候, 就通過它去檢查當(dāng)前請(qǐng)求是否為文件上傳請(qǐng)求(通過CommonsMultipartResolver的isMultipart方法)。
(2)如果當(dāng)前請(qǐng)求不是MultipartHttpServletReques并且不包含MultipartException異常,那么,就通過CommonsMultipartResolver去處理當(dāng)前請(qǐng)求(通過調(diào)用resolveMultipart方法將當(dāng)前請(qǐng)求包裝為MultipartHttpServletRequest),返回包裝后的請(qǐng)求。
(3)返回當(dāng)前請(qǐng)求(未經(jīng)處理的請(qǐng)求)。
接下來我們重點(diǎn)看看,spring是如何判斷是否為文件上傳的請(qǐng)求的:
CommonsMultipartResolver:
@Override public boolean isMultipart(HttpServletRequest request) { return (request != null && ServletFileUpload.isMultipartContent(request)); }這兒直接使用了Apache 的commons-fileupload中的ServletFileUpload, 那我們就來看看它究竟何許人也:
ServletFileUpload:
public static final boolean isMultipartContent( HttpServletRequest request) { if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) { return false; } return FileUploadBase.isMultipartContent(new ServletRequestContext(request)); }以上代碼說明:
(1)當(dāng)前請(qǐng)求必須是post方法。
(2)如果是post方法,就通過 FileUploadBase 去進(jìn)一步檢測(cè)。
FileUploadBase:
public static final boolean isMultipartContent(RequestContext ctx) { String contentType = ctx.getContentType(); if (contentType == null) { return false; } if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) { return true; } return false; }以上方法說明:
只有當(dāng)當(dāng)前請(qǐng)求的contentType是 "multipart/" 的時(shí)候,才會(huì)將此請(qǐng)求當(dāng)做文件上傳的請(qǐng)求。
總結(jié):
綜合來看,spring其實(shí)是通過Apache的 commons-fileupload來檢測(cè)請(qǐng)求是否為文件上傳的請(qǐng)求。而commons-fileupload又是通過如下兩個(gè)條件來判斷:
1. 請(qǐng)求方法必須是 post.
2. 請(qǐng)求的contentType 必須設(shè)置為以 "multipart/" 開頭。
這下你該明白為什么我們?cè)谏蟼魑募臅r(shí)候必須要做的那些設(shè)置了吧。
好啦,回到文章開始的問題:
org.springframework.web.multipart.MultipartException: The current request is not a multipart request
這個(gè)問題是怎么導(dǎo)致的呢?
其實(shí)springmvc 在處理方法入?yún)⒌臅r(shí)候,發(fā)現(xiàn)了你的一個(gè)參數(shù)為 MultipartFile 類型或者是其數(shù)組或者包含他的容器類型,那么springmvc 就會(huì)通過上面類似的方法去檢驗(yàn)(通過contentType)。代碼如下:
private void assertIsMultipartRequest(HttpServletRequest request) { String contentType = request.getContentType(); if (contentType == null || !contentType.toLowerCase().startsWith("multipart/")) { throw new MultipartException("The current request is not a multipart request"); } }那么這個(gè)問題該如何解決呢?
(1)ContentType 必須設(shè)置為 multipart/ 開頭的(通常是multipart/form-data)。我之所以會(huì)遇到這個(gè)問題,其實(shí)是因?yàn)樵贏PP請(qǐng)求的時(shí)候明明使用的multipart/form-data,但是卻始終通不過,嘗試用瀏覽器OK。
(2)在保證(1)的情況,如果還是這個(gè)錯(cuò)誤,那么通過上面的分析,其實(shí)也很好解決,怎么解決?
spring在處理入?yún)⒌臅r(shí)候, 不是遇到MultipartFile相關(guān)就會(huì)先去校驗(yàn)么,OK,利用這個(gè),那么咱們可以改寫入?yún)ⅲㄖ苯咏邮赵膆ttp request),然后自己手動(dòng)去校驗(yàn)啊對(duì)吧,這不就繞過了。當(dāng)繞過這一步之后,springmvc會(huì)通過之前分析的代碼,對(duì)收到的請(qǐng)求進(jìn)行校驗(yàn)轉(zhuǎn)換,最終也會(huì)得到MultipartHttpServletRequest。修改如下:
@RequestMapping(value = "/upload", method = RequestMethod.POST)public ResultView upload(HttpServletRequest request) { if (request instanceof MultipartHttpServletRequest) { // process }}OK, 這樣就通過了。
好啦,本篇就先寫到這兒,下篇將向大家談?wù)剆pringmvc上傳文件的效率問題。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注