Servlet3笔记-文件上传 | 木杉的博客中提到过,Servlet3.0以前,处理上传文件是非常麻烦的。而SpringMVC包装了这个复杂性,我们可以很方便的在任何Servlet版本上方便的处理上传文件。

原理

在Servlet3.0之前获取文件的方式,是通过手动解析Request来实现的,使用commons-fileupload.jarcommons-io.jar这两个依赖解析Multipart格式的请求体,具体的方法网上很多,比如这篇

而SpringMVC就是吧这些代码帮我们写好了。原理是一样的。在处理请求时,Spring会看是否存在一个叫做multipartResolver的Bean,如果有,那么在处理请求时,会使用对应的multipartResolver来处理格式为multipart/form-data的请求体,同时把普通的HttpServletRequest替换为可以操作上传文件的MultipartHttpServletRequest

使用

因为Spring提供的CommonsMultipartResolver使用Apache Commons FileUpload来实现请求解析,所以需要添加依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Apache Commons FileUpload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>

<!-- Apache Commons IO -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>

然后添加MultipartResolver的Bean:

1
2
3
4
5
6
7
8
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setDefaultEncoding("utf-8");
resolver.setMaxUploadSize(5 * 1024 * 1024);
resolver.setMaxInMemorySize(512 * 1024);
return resolver;
}

xml配置的话这么写:

1
2
3
4
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- setting maximum upload size -->
<beans:property name="maxUploadSize" value="100000" />
</beans:bean>

然后对应的Controller处理方法上,可以使用@RequestParam("file") MultipartFile file@RequestParam("file") MultipartFile[] files来获取单个或者多个上传文件。

同时,传入方法的Request具体类型变为MultipartHttpServletRequest,可以根据需要进行类型转换后调用MultipartHttpServletRequest上的方法。

原理之代码层面

DispatcherServlet在初始化时会尝试加载容器中的MultipartResolver,流程如下:

具体读取代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
"': no multipart request handling provided");
}
}
}

很简单,就是读取名字为MULTIPART_RESOLVER_BEAN_NAME指定的(默认为multipartResolver),类型为MultipartResolver的Bean。

在处理请求时,DispatcherServlet会调用方法来处理Request:

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
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 (hasMultipartException(request) ) {
logger.debug("Multipart resolution failed for current request before - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
try {
return this.multipartResolver.resolveMultipart(request);
}
catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
}
else {
throw ex;
}
}
}
}
// If not returned before: return original request.
return request;
}

参考资料