深刻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上传文件的效率问题。

相关文章
相关标签/搜索