深入springMVC------文件上传源码解析(上篇)
本文内容纲要:
最近在项目中,使用springmvc进行上传文件时,出现了一个问题:
org.springframework.web.multipart.MultipartException:Thecurrentrequestisnotamultipartrequest
``....
以上堆栈信息省略。
乍看一下,没啥值得讨论的地方,就是说当前这个请求不是一个multipartrequest,也就是说不是上传文件的请求。但是,这结果还是令我稍感意外,为什么呢?因为,我本意是将文件这个参数作为非必要参数,类似下面这样:
@RequestMapping(value="/upload",method=RequestMethod.POST)
publicResultViewupload(@RequestParam(value="file",required=false)MultipartFilefile)
spring抛出上面的异常,就违背了我的本意,我明明设置了“required=false”,为什么还是不行?于是,带着疑问去看了一下spring的源码,下面就跟大家分享一下springmvc对于文件上传的处理。
--------------------------------------------------我是华丽的分割线-------------------------------------------------------
在springmvc通过DispatcherServlet处理请求时,会调用到doDispatch这个方法,当然这也是springmvc处理请求最核心的方法:
protectedvoiddoDispatch(HttpServletRequestrequest,HttpServletResponseresponse)throwsException{
HttpServletRequestprocessedRequest=request;
HandlerExecutionChainmappedHandler=null;
booleanmultipartRequestParsed=false;
WebAsyncManagerasyncManager=WebAsyncUtils.getAsyncManager(request);
try{
ModelAndViewmv=null;
ExceptiondispatchException=null;
try{
processedRequest=checkMultipart(request);
multipartRequestParsed=(processedRequest!=request);
上面就是给出的有关上传文件的代码片段,看以看到,当spring处理请求的时候,首先第一步就去检查当前请求是否为上传文件的请求,那么,它是怎么检查的呢,接着往下看:
protectedHttpServletRequestcheckMultipart(HttpServletRequestrequest)throwsMultipartException{
if(this.multipartResolver!=null&&this.multipartResolver.isMultipart(request)){
if(WebUtils.getNativeRequest(request,MultipartHttpServletRequest.class)!=null){
logger.debug("RequestisalreadyaMultipartHttpServletRequest-ifnotinaforward,"+
"thistypicallyresultsfromanadditionalMultipartFilterinweb.xml");
}
elseif(request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE)instanceofMultipartException){
logger.debug("Multipartresolutionfailedforcurrentrequestbefore-"+
"skippingre-resolutionforundisturbederrorrendering");
}
else{
returnthis.multipartResolver.resolveMultipart(request);
}
}
//Ifnotreturnedbefore:returnoriginalrequest.
returnrequest;
}
通过以上方法,我们可以看到如下逻辑:
(1)当MultipartResolver不为null的时候,就通过它去检查当前请求是否为文件上传请求(通过CommonsMultipartResolver的isMultipart方法)。
(2)如果当前请求不是MultipartHttpServletReques并且不包含MultipartException异常,那么,就通过CommonsMultipartResolver去处理当前请求(通过调用resolveMultipart方法将当前请求包装为MultipartHttpServletRequest),返回包装后的请求。
(3)返回当前请求(未经处理的请求)。
接下来我们重点看看,spring是如何判断是否为文件上传的请求的:
CommonsMultipartResolver:
@Override
publicbooleanisMultipart(HttpServletRequestrequest){
return(request!=null&&ServletFileUpload.isMultipartContent(request));
}
这儿直接使用了Apache的commons-fileupload中的ServletFileUpload,那我们就来看看它究竟何许人也:
ServletFileUpload:
publicstaticfinalbooleanisMultipartContent(
HttpServletRequestrequest){
if(!POST_METHOD.equalsIgnoreCase(request.getMethod())){
returnfalse;
}
returnFileUploadBase.isMultipartContent(newServletRequestContext(request));
}
以上代码说明:
(1)当前请求必须是post方法。
(2)如果是post方法,就通过FileUploadBase去进一步检测。
FileUploadBase:
publicstaticfinalbooleanisMultipartContent(RequestContextctx){
StringcontentType=ctx.getContentType();
if(contentType==null){
returnfalse;
}
if(contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)){
returntrue;
}
returnfalse;
}
以上方法说明:
只有当当前请求的contentType是"multipart/"的时候,才会将此请求当做文件上传的请求。
总结:
综合来看,spring其实是通过Apache的commons-fileupload来检测请求是否为文件上传的请求。而commons-fileupload又是通过如下两个条件来判断:
-
请求方法必须是post.
-
请求的contentType必须设置为以"multipart/"开头。
这下你该明白为什么我们在上传文件的时候必须要做的那些设置了吧。
好啦,回到文章开始的问题:
org.springframework.web.multipart.MultipartException:Thecurrentrequestisnotamultipartrequest
这个问题是怎么导致的呢?
其实springmvc在处理方法入参的时候,发现了你的一个参数为MultipartFile类型或者是其数组或者包含他的容器类型,那么springmvc就会通过上面类似的方法去检验(通过contentType)。代码如下:
privatevoidassertIsMultipartRequest(HttpServletRequestrequest){
StringcontentType=request.getContentType();
if(contentType==null||!contentType.toLowerCase().startsWith("multipart/")){
thrownewMultipartException("Thecurrentrequestisnotamultipartrequest");
}
}
那么这个问题该如何解决呢?
(1)ContentType必须设置为multipart/开头的(通常是multipart/form-data)。我之所以会遇到这个问题,其实是因为在APP请求的时候明明使用的multipart/form-data,但是却始终通不过,尝试用浏览器OK。
(2)在保证(1)的情况,如果还是这个错误,那么通过上面的分析,其实也很好解决,怎么解决?
spring在处理入参的时候,不是遇到MultipartFile相关就会先去校验么,OK,利用这个,那么咱们可以改写入参(直接接收原生的httprequest),然后自己手动去校验啊对吧,这不就绕过了。当绕过这一步之后,springmvc会通过之前分析的代码,对收到的请求进行校验转换,最终也会得到MultipartHttpServletRequest。修改如下:
@RequestMapping(value="/upload",method=RequestMethod.POST)
publicResultViewupload(HttpServletRequestrequest){
if(requestinstanceofMultipartHttpServletRequest){
//process
}
}
OK,这样就通过了。
好啦,本篇就先写到这儿,下篇将向大家谈谈springmvc上传文件的效率问题。
本文内容总结:
原文链接:https://www.cnblogs.com/dongying/p/4388464.html