最近在项目中,使用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:

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

深入springMVC------文件上传源码解析(上篇)的更多相关文章

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

    在上篇<深入springMVC------文件上传源码解析(上篇) >中,介绍了springmvc文件上传相关.那么本篇呢,将进一步介绍springmvc 上传文件的效率问题. 相信大部分 ...

  2. ajaxFileUpload 实现多文件上传(源码)

    按照原ajaxFileUpload.js是不能多文件上传的.需要对源码进行修改:主要修改了fileElementId部分 具体参考 https://blog.csdn.net/itmyhome1990 ...

  3. SpringMVC文件上传 Excle文件 Poi解析 验证 去重 并批量导入 MYSQL数据库

    SpringMVC文件上传 Excle文件 Poi解析并批量导入 MYSQL数据库  /** * 业务需求说明: * 1 批量导入成员 并且 自主创建账号 * 2 校验数据格式 且 重复导入提示 已被 ...

  4. springmvc文件上传下载简单实现案例(ssm框架使用)

    springmvc文件上传下载实现起来非常简单,此springmvc上传下载案例适合已经搭建好的ssm框架(spring+springmvc+mybatis)使用,ssm框架项目的搭建我相信你们已经搭 ...

  5. springmvc文件上传AND jwt身份验证

    SpringMVC文件上传 思路:1.首先定义页面,定义多功能表单(enctype=“multipart/form-data”)2.在Controller里面定义一个方法,用参数(MultipartF ...

  6. SpringMVC文件上传下载(单文件、多文件)

    前言 大家好,我是bigsai,今天我们学习Springmvc的文件上传下载. 文件上传和下载是互联网web应用非常重要的组成部分,它是信息交互传输的重要渠道之一.你可能经常在网页上传下载文件,你可能 ...

  7. 18 SpringMVC 文件上传和异常处理

    1.文件上传的必要前提 (1)form 表单的 enctype 取值必须是:multipart/form-data(默认值是:application/x-www-form-urlencoded) en ...

  8. TZ_06_SpringMVC_传统文件上传和SpringMVC文件上传方式

    1.传统文件上传方式 <!-- 文件上传需要的jar --> <dependency> <groupId>commons-fileupload</groupI ...

  9. maven上传源码到私服

    上传源码 项目中采用了分模块的方式构建,直接将maven-source-plugin写到父pom中,尝试了很多次发现源码一直不能上传到私服中,纠结了很长时间才发现原来多模块项目和普通一个项目的配置是有 ...

随机推荐

  1. 【转载】移动web开发经验总结

    本文出自: http://blog.163.com/hsb001_mobile/blog/static/15524028020111177221254/ 1.-webkit-tap-highlight ...

  2. 浅析Java.lang.Runtime类

    一.概述      Runtime类封装了运行时的环境.每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接.      一般不能实例化一个Runtime对象, ...

  3. redis.conf配置详细翻译解析

    # redis 配置文件示例 # 当你需要为某个配置项指定内存大小的时候,必须要带上单位, # 通常的格式就是 1k 5gb 4m 等酱紫: # # 1k => 1000 bytes # 1kb ...

  4. [转]CocoaPods安装和使用教程

    转载地址:http://code4app.com/article/cocoapods-install-usage 目录 CocoaPods是什么? 如何下载和安装CocoaPods? 如何使用Coco ...

  5. Android 进程常驻----native保活5.0以上方案推演过程以及代码

    正文: 上一篇我们通过父子进程间建立双管道,来监听进程死掉,经过测试,无耗电问题,无内存消耗问题,可以在设置中force close下成功拉起,也可以在获取到root权限的360/cleanmaste ...

  6. 在Excel中制作复合饼图

    在Excel中插入饼图时有时会遇到这种情况,饼图中的一些数值具有较小的百分比,将其放到同一个饼图中难以看清这些数据,这时使用复合条饼图就可以提高小百分比的可读性. 文中的复合饼图只是方便以后记忆,故不 ...

  7. 《.Net 的冰与火之歌》寄雁传书,你必须知道的C#参数知识大盘点

    引言 参数,也叫参变量,是一个变量.在方法签名中随处可见,实现了不同方法间对于数据的寄雁传书,基本上充斥在代码的各个角落里.在方法签名或者原型中,方法名称后的括号包含方法的参数及其类型的完整列表.参数 ...

  8. strom的使用01

    1.strom的安装和测试 1.1 搭建zookeeper集群 参考hadoop2.0初识1.3中的1.4配置zookeeper集群和自动故障转移 1.2 安装storm依赖的软件 sudo yum ...

  9. PHP 编译安装

    1, 获取源码wget -O php.tar.gz http://am1.php.net/get/php-5.6.27.tar.gz/from/this/mirrortar zxvf php.tar. ...

  10. td在relative模式下,IE9不显示border

    方法一 .thisTd {    background-clip: padding-box;     position:relative; } 方法二 .thisTd {   z-index=-1; ...