深刻springMVC------文件上傳源碼解析(上篇)

最近在項目中,使用springmvc 進行上傳文件時,出現了一個問題:web

org.springframework.web.multipart.MultipartException: The current request is not a multipart requestspring

....數組

以上堆棧信息省略。瀏覽器

乍看一下,沒啥值得討論的地方,就是說當前這個請求不是一個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上傳文件的效率問題。

相關文章
相關標籤/搜索