最近在項目中,使用springmvc 進行上傳文件時,出現了一個問題:web
org.springframework.web.multipart.MultipartException: The current request is not a multipart request
spring
....數組
以上堆棧信息省略。瀏覽器
乍看一下,沒啥值得討論的地方,就是說當前這個請求不是一個multipart request,也就是說不是上傳文件的請求。可是,這結果仍是令我稍感意外,爲何呢?由於,我本意是將文件這個參數做爲非必要參數,相似下面這樣:mvc
@RequestMapping(value = "/upload", method = RequestMethod.POST) public ResultView upload(@RequestParam(value = "file", required = false) MultipartFile file)
spring拋出上面的異常,就違背了個人本意,我明明設置了 「required = false」, 爲何仍是不行? 因而,帶着疑問去看了一下spring的源碼,下面就跟你們分享一下spring mvc對於文件上傳的處理。app
--------------------------------------------------我是華麗的分割線-------------------------------------------------------async
在spring mvc經過DispatcherServlet處理請求時,會調用到 doDispatch這個方法,固然這也是spring mvc處理請求最核心的方法:ide
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);
上面就是給出的有關上傳文件的代碼片斷,看以看到,當spring處理請求的時候,首先第一步就去檢查當前請求是否爲上傳文件的請求,那麼,它是怎麼檢查的呢,接着往下看:post
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; }
經過以上方法,咱們能夠看到以下邏輯:ui
(1)當 MultipartResolver 不爲null的時候, 就經過它去檢查當前請求是否爲文件上傳請求(經過CommonsMultipartResolver的isMultipart方法)。
(2)若是當前請求不是MultipartHttpServletReques而且不包含MultipartException異常,那麼,就經過CommonsMultipartResolver去處理當前請求(經過調用resolveMultipart方法將當前請求包裝爲MultipartHttpServletRequest),返回包裝後的請求。
(3)返回當前請求(未經處理的請求)。
接下來咱們重點看看,spring是如何判斷是否爲文件上傳的請求的:
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)當前請求必須是post方法。
(2)若是是post方法,就經過 FileUploadBase 去進一步檢測。
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; }
以上方法說明:
只有噹噹前請求的contentType是 "multipart/" 的時候,纔會將此請求當作文件上傳的請求。
總結:
綜合來看,spring實際上是經過Apache的 commons-fileupload來檢測請求是否爲文件上傳的請求。而commons-fileupload又是經過以下兩個條件來判斷:
1. 請求方法必須是 post.
2. 請求的contentType 必須設置爲以 "multipart/" 開頭。
這下你該明白爲何咱們在上傳文件的時候必需要作的那些設置了吧。
好啦,回到文章開始的問題:
org.springframework.web.multipart.MultipartException: The current request is not a multipart request
這個問題是怎麼致使的呢?
其實springmvc 在處理方法入參的時候,發現了你的一個參數爲 MultipartFile 類型或者是其數組或者包含他的容器類型,那麼springmvc 就會經過上面相似的方法去檢驗(經過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"); } }
那麼這個問題該如何解決呢?
(1)ContentType 必須設置爲 multipart/ 開頭的(一般是multipart/form-data)。我之因此會遇到這個問題,實際上是由於在APP請求的時候明明使用的multipart/form-data,可是卻始終通不過,嘗試用瀏覽器OK。
(2)在保證(1)的狀況,若是仍是這個錯誤,那麼經過上面的分析,其實也很好解決,怎麼解決?
spring在處理入參的時候, 不是遇到MultipartFile相關就會先去校驗麼,OK,利用這個,那麼我們能夠改寫入參(直接接收原生的http request),而後本身手動去校驗啊對吧,這不就繞過了。當繞過這一步以後,springmvc會經過以前分析的代碼,對收到的請求進行校驗轉換,最終也會獲得MultipartHttpServletRequest。修改以下:
@RequestMapping(value = "/upload", method = RequestMethod.POST) public ResultView upload(HttpServletRequest request) { if (request instanceof MultipartHttpServletRequest) { // process } }
OK, 這樣就經過了。
好啦,本篇就先寫到這兒,下篇將向你們談談springmvc上傳文件的效率問題。