最近在项目中,使用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上传文件的效率问题。