SpringMVC文件上传模块失败

BUG

在编写文件上传模块时,服务器报了个状态码为500的错误:Unable to process parts as no multi-part configuration has been provided,由于之前没有处理过SpringMVC的图片上传,所以也看不懂是什么玩意儿(后来我才知道,我的配置里少了一行代码,直接导致这个错误)。

背景知识

我在前端写了个表单,用来选择文件和上传:

1
2
3
4
5
<form method="post" action="<c:url value="/student/commit"/>" enctype="multipart/form-data">
<input type="text" name="name"/>
<input type="file" name="file"/>
<input type="submit"/>
</form>

在选择了文件,并按submit按钮后,前端就会将文件POST到后台服务器(路径是localhost:8080/student/commit)。

后台服务器的controller里有对应的处理方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping(value = "/commit", method = RequestMethod.POST)
public String commit(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) {

logger.info(name);
try {
logger.info(file.getInputStream().toString());
} catch (IOException e) {
e.printStackTrace();
}

return "/student/commit";
}

它会打印前端传来的文件信息。

同时我还在SpringMVC的配置文件(spring-web.xml)里配置了对文件的支持:

1
2
3
4
<!--配置文件上传-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>

它令SpringMVC具有了识别文件上传的功能。

问题之谜

这么配置后,我运行项目,选择文件,点击上传按钮,浏览器里显示状态码为500,内容为Unable to process parts as no multi-part configuration has been provided的错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
HTTP Status 500 - Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided

type Exception report

message Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided

description The server encountered an internal error that prevented it from fulfilling this request.

exception

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:979)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869)
javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
root cause

org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:100)
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:78)
org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:76)
org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1089)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:928)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869)
javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
root cause

java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided
org.apache.catalina.connector.Request.parseParts(Request.java:2742)
org.apache.catalina.connector.Request.getParts(Request.java:2708)
org.apache.catalina.connector.RequestFacade.getParts(RequestFacade.java:1096)
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:85)
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:78)
org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:76)
org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1089)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:928)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869)
javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
note The full stack trace of the root cause is available in the Apache Tomcat/9.0.0.M6 logs.

Apache Tomcat/9.0.0.M6

搜寻其他人的方案,我发现我的方案并没有什么特殊的,到底问题出在哪里?

错误里的no multi-part configuration has been provided是什么意思,未提供文件上传的配置?

可我分明在spring-web.xml里加入了相应的配置。

我再仔细看,Could not parse multipart servlet request,没有servlet的配置?

谜题之解

我这才意识到,SpringMVC是依赖Servlet的,虽然SpringMVC能识别Servlet,但它底层的Servlet也许还不能识别文件上传。

于是我赶紧翻开SpringMVC官网的refference去看文档,果然,我发现了一些猫腻:

Once Servlet 3.0 multipart parsing has been enabled in one of the above mentioned ways you can add the StandardServletMultipartResolver to your Spring configuration

在配置SpringMVC的StandardServletMultipartResolver之前必须先配置Servlet,官网提供的方案有:

  1. mark the DispatcherServlet with a “multipart-config” section in web.xml(在web.xml文件中的DispatcherServlet元素里,加上一个multipart-config)
  2. with a javax.servlet.MultipartConfigElement in programmatic Servlet registration
  3. in case of a custom Servlet class possibly with a javax.servlet.annotation.MultipartConfig annotation on your Servlet class

我选择了第一种方案,打开webapp/WEB-INFO目录下的web.xml,往DispatcherServlet里加了一行

1
<multipart-config/>

再运行项目,就可以正常地上传文件了。

web.xml全部内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-complete="true">


<!--配置DispatcherServlet-->
<servlet>
<servlet-name>my-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<!--让Servlet支持文件上传-->
<multipart-config/>

<!--springMVC需要加载的文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-*.xml</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>my-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>


</web-app>

直到最后,我才发现,解决这个BUG,只需要一行代码。