深入springMVC------文件上传源码解析(上篇)

南轲梦
随笔- 29  文章- 0  评论- 143  博客园   首页   新随笔   联系   管理   订阅  订阅 深入springMVC------文件上传源码解析(上篇)

最近在项目中,使用springmvc 进行上传文件时,出现了一个问题:

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

....

以上堆栈信息省略。

乍看一下,没啥值得讨论的地方,就是说当前这个请求不是一个multipart request,也就是说不是上传文件的请求。但是,这结果还是令我稍感意外,为什么呢?因为,我本意是将文件这个参数作为非必要参数,类似下面这样:

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public ResultView upload(@RequestParam(value = "file", required = false) MultipartFile file)

spring抛出上面的异常,就违背了我的本意,我明明设置了 “required = false”, 为什么还是不行? 于是,带着疑问去看了一下spring的源码,下面就跟大家分享一下spring mvc对于文件上传的处理。

--------------------------------------------------我是华丽的分割线-------------------------------------------------------

在spring mvc通过DispatcherServlet处理请求时,会调用到 doDispatch这个方法,当然这也是spring mvc处理请求最核心的方法:

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处理请求的时候,首先第一步就去检查当前请求是否为上传文件的请求,那么,它是怎么检查的呢,接着往下看:

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;}

通过以上方法,我们可以看到如下逻辑:

(1)当 MultipartResolver 不为null的时候, 就通过它去检查当前请求是否为文件上传请求(通过CommonsMultipartResolver的isMultipart方法)。

(2)如果当前请求不是MultipartHttpServletReques并且不包含MultipartException异常,那么,就通过CommonsMultipartResolver去处理当前请求(通过调用resolveMultipart方法将当前请求包装为MultipartHttpServletRequest),返回包装后的请求。

(3)返回当前请求(未经处理的请求)。

接下来我们重点看看,spring是如何判断是否为文件上传的请求的:

CommonsMultipartResolver:

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

posted on 2015-04-03 11:26 南轲梦 阅读(41432) 评论(4) 编辑 收藏 发表评论    #1楼 支持一下 支持(0) 反对(0) http://pic.cnblogs.com/face/60039/20150312190409.png    #2楼 解决了我的问题! 支持(1) 反对(0) http://pic.cnblogs.com/face/753225/20180115145355.png    #3楼 对我有用 支持(0) 反对(0)    #4楼 解决了我的问题 支持(0) 反对(0) http://pic.cnblogs.com/face/620936/20170119143545.png 刷新评论 刷新页面 返回顶部 注册用户登录后才能发表评论,请 登录 或 注册, 访问网站首页。 昵称: 南轲梦
园龄: 4年9个月
粉丝: 459
关注: 0 +加关注

搜索

   

常用链接

  • 我的随笔
  • 我的评论
  • 我的参与
  • 最新评论
  • 我的标签

我的标签

  • Mybatis深入浅出系列(13)
  • linux(6)
  • shell(5)
  • Spring Security(4)
  • java容器(4)
  • springmvc源码(4)
  • 权限(2)
  • mysql(1)
  • maven(1)

随笔分类

  • javase(4)
  • linux(6)
  • maven(1)
  • mybatis(12)
  • spring security(2)
  • springmvc(6)

随笔档案

  • 2017年3月 (1)
  • 2017年2月 (5)
  • 2017年1月 (2)
  • 2016年12月 (2)
  • 2016年11月 (1)
  • 2015年4月 (2)
  • 2015年2月 (1)
  • 2014年12月 (1)
  • 2014年11月 (2)
  • 2014年10月 (12)

积分与排名

  • 积分 - 84963
  • 排名 - 6364

最新评论

  • 1. Re:深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)
  • 先不管看没看懂,看评论是个好文章[]~( ̄▽ ̄)~*
  • --youxiu326
  • 2. Re:Mybatis实战之TypeHandler高级进阶
  • 看不到图片了
  • --SpringBoot1
  • 3. Re:shell编程其实真的很简单(五)
  • 感谢楼主
  • --扁担猫
  • 4. Re:深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)
  • 感谢楼主!有个问题,如果我想利用mybatis获取动态SQL,但是并不想通过spring+mybatis来执行SQL,因为我想自己控制事务的同时还想动态拿到SQL,该如何使用?
  • --侯玺泽
  • 5. Re:深入springMVC源码------文件上传源码解析(下篇)
  • getInputStream() 方法哪里多了一步转换操作了?明明没有转啊 InputStream inputStream = this.fileItem.getInputStream(); 只是用.......
  • --花溪的小石头

阅读排行榜

  • 1. 深入浅出Mybatis系列(八)---mapper映射文件配置之select、resultMap(91582)
  • 2. 深入浅出Mybatis系列(七)---mapper映射文件配置之insert、update、delete(66710)
  • 3. 深入浅出Mybatis系列(九)---强大的动态SQL(64933)
  • 4. shell编程其实真的很简单(一)(55223)
  • 5. 深入浅出Mybatis系列(五)---TypeHandler简介及配置(mybatis源码篇)(49420)

评论排行榜

  • 1. 深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)(19)
  • 2. 深入浅出Mybatis系列(一)---Mybatis入门(14)
  • 3. 深入浅出Mybatis系列(八)---mapper映射文件配置之select、resultMap(11)
  • 4. 深入浅出Mybatis系列(九)---强大的动态SQL(10)
  • 5. 话说Spring Security权限管理(源码)(10)

推荐排行榜

  • 1. 深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)(23)
  • 2. 深入浅出Mybatis系列(九)---强大的动态SQL(20)
  • 3. 深入springMVC------文件上传源码解析(上篇)(15)
  • 4. shell编程其实真的很简单(一)(14)
  • 5. 深入浅出Mybatis系列(一)---Mybatis入门(10)
Copyright ©2019 南轲梦


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部