Spring 内置的 multipart 文件上传支持

Spring build-in multipart fileupload support

Spring 内置了对 web 应用 multipart(文件上传) 的支持,即通过可拔插的 MultipartResolver 对象实现对 multipart 的处理。该对象定义在 org.springframework.web.multipart 包内,它是按照 RFC 1867 设计的一个策略接口,自 Spring 2.5 开始,Sping 内包含了一个它的具体实现类,即 org.springframework.web.multipart.commons.CommonsMultipartResolver,该实现类是建立在 Jakarta Commons FileUpload 基础上的,因此在使用时需要引入对 ‘commons-fileupload.jar’ 的依赖。

默认情况下,Spring 是不对 multipart 进行处理的,你可以在你的 web 应用上下文配置中添加一个 MultipartResolver 的实现来启用对 multipart 的支持,这样容器就会对每个请求进行检查,如果发现请求包含了 multipart 数据,就会使用配置的 MultipartResolver 实现来对请求进行处理。

MultipartResolver 的用法

下面例子演示了如何使用 Sping 自带的实现类 CommonsMultipartResolver 来启用 multipart 处理,在 Spring 应用上下文中添加如下配置:

1
2
3
4
5
6
7
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- one of the properties available, the maximum file size in bytes, here is 100KB -->
<property name="maxUploadSize" value="100000"/>
</bean>

添加了该配置,当 Spring 的 DispatcherServlet 收到包含 multipart 的请求后,配置的 MultipartResolver 就会被调用来处理这类请求,它会将当前的 HttpServletRequest 包装成一个 MultipartHttpServletRequest 来支持 multipart 的处理。从 MultipartHttpServletRequest 中你可以获取到 multipart 的数据内容,从而让你的 Controller 能够直接访问请求中包含的文件。

处理表单上传文件请求示例

假设我们创建的表单页面如下:

1
2
3
4
5
6
7
8
9
10
<html>
<head><title>File Upload</title></head>
<body>
<form method="post" action="upload.do" enctype="multipart/form-data">
<input type="text" name="fileName" />
<input type="file" name="fileData" />
<input type="submit" />
</form>
</body>
</html>

我们给 form 标签添加了 enctype 属性,其值为 multipart/form-data,该属性是用来告知浏览器用什么方法对 multipart 字段内容进行编码。

相应的,我们创建一个 bean 来作为表单提交数据的容器(即MVC的Model):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class FileUpload {
private String fileName;
private CommonsMultipartFile fileData;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public CommonsMultipartFile getFileData() {
return fileData;
}
public void setFileData(CommonsMultipartFile fileData) {
this.fileData = fileData;
}
}

可以看到这个 POJO 完全对应表单的两个字段,且对应上传的文件的字段是 CommonsMultipartFile 类型,它是 Jakarta Commons FileUpload 中 MultipartFile 的一个实现。

假设我们的 DispatcherServlet 配置如下:

1
2
3
4
5
6
7
8
9
10
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>

即所有发送到以 ‘.do’ 结尾的URL的请求都将由 default 这个 DispatcherServlet 进行处理转发。

接下来说明如何实现一个 Spring 的 Controller 来处理文件上传的请求。

首先来看 default-servlet.xml 的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!-- Auto scan controllers -->
<context:component-scan base-package="sample.upload.controller" />
<!-- Enable multipart resolver to handle fileuploads -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- the maximum file size is 50KB -->
<property name="maxUploadSize" value="50000" />
</bean>
</beans>

除了前面提到的 CommonsMultipartResolver 之外,我们添加了 context:component-scan 标签,并配置了其 base-package 属性。该标签可以让 Spring 自动扫描具有 Spring 注解的组件并载入上下文,而 base-package 就是需要扫描的包路径。

用来处理文件上传的 Controller 实现如下,省略了 imports 和一部分代码:

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
package sample.upload.controller;
@Controller
@RequestMapping(value="/upload.do")
public class FileUploadController {
@RequestMapping(method=RequestMethod.POST)
public void upload(FileUpload fileUpload, BindingResult result) {
if (result.hasErrors()) {
StringBuilder info = new StringBuilder();
for (ObjectError error : result.getAllErrors()) {
info.append("{").append(error.getCode()).append(": ")
.append(error.getDefaultMessage()).append("} ");
}
logger.error(info.toString());
}
if (logger.isDebugEnabled()) {
logger.debug("File Name: " + fileUpload.getFileName());
if (fileUpload.getFileData() != null) {
logger.debug("Original Filename: "
+ fileUpload.getFileData().getOriginalFilename());
logger.debug("File Type: " + fileUpload.getFileData().getContentType());
logger.debug("File Size: " + fileUpload.getFileData().getSize());
}
}
if (fileUpload.getFileData() != null
&& fileUpload.getFileData().getBytes() != null) {
// do sth. with byte[] of the file.
}
}
}

这个类通过添加 @Controller 注解将其声明为一个 Spring 的组件,这样我们前面配置的自动扫描就会自动将其载入上下文,我们实现了 upload 方法,并通过 @RequestMapping 注解使其处理通过 POST 方法发送到 upload.do 的 HTTP 请求,Spring 会自动解析请求中的参数,并为我们生成 FileUpload 这个 bean 的实例。CommonsMultipartFile 提供了多个 get… 方法可以让我们取得文件的相关信息和数据,接下来就可以根据自己的需要来处理上传的文件了。