Spring MVC 中文件上传
为了能上传文件,必须将 from 表单的 method 设置为 POST,并将 enctype 设置为 multipart/form-data 。
实现文件上传的 “底层” 方案有 2 种:
-
使用 Apache Commons FileUpload 包
-
使用 Servlet 3.1 内置的文件上传功能
无论你的底层是使用上述的哪种方案,Spring MVC 都对它们作出了『包装』,让 Spring MVC 中的上传文件的代码简化而统一:提供一个 MultipartResolver,并将 <input type=“file” …> 类型的请求参数绑定到请求处理方法的 MultipartFile 类型的参数上。(两者具体的类型有所不同)
这需要提前说明以下,Spring MVC 利用 Servlet 3.1 内置的文件上传功能上传文件时,有个小问题。有人发现无法将它所用到的 StandardMultipartResolver 的编码从默认的 iso-8859-1 改为 UTF-8 。也有人分析,在使用 tomcat 7 时会出现这个问题,并认为这是 tomcat 7 的 bug 。所有,简单起见,建议优先考虑 commons-fileupload 方案。
具体讨论可参见:stakoverflow 。
利用 commons-fileupload 文件上传
利用 commons-fileupload 文件上传需要利用引入 commons-fileupload 包(它依赖于 commons-io 包)
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>
Spring MVC 是通过 MultipartResolver 的 JavaBean 提供、支持文件上传功能。commons-fileupload 中该接口的实现类是 CommonsMultipartResolver 。
简单来说,CommonsMultipartResolver 是 Spring MVC 去利用 commons-fileupload 实现上传功能的『桥梁』。
在 spring-web.xml 中添加如下配置,让 Spring 负责创建并初始化该 Bean 。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="104857600" />
<property name="maxInMemorySize" value="4096" />
<property name="defaultEncoding" value="UTF-8"/>
<!-- 更多配置根据具体需求进一步再学习/使用 -->
</bean>
至此,配置结束。在 Spring MVC 的 Controller 中,Spring MVC 就可以将用户上传的数据绑定到 CommonsMultipartFile 类型的参数上。
注意
此处的@RequestParam()
不能省略,即便是 name 与 name 一致。
@RequestMapping("/upload.do")
public String upload(String username,
String password,
@RequestParam("uploadfile") CommonsMultipartFile uploadfile) throws IOException {
log.info("{}", uploadfile.getName());
log.info("{}", uploadfile.getOriginalFilename());
String path = "D:/" + new Date().getTime() + uploadfile.getOriginalFilename();
uploadfile.transferTo(new File(path));
return "";
}
CommonsMultipartFile 支持如下功能:
方法 | 说明 |
---|---|
byte[] getBytes() | 以字节数组的形式返回文件的内容 |
String getContentType() | 返回文件的内容类型 |
InputStream getInputStream() | 返回一个 InputStream,可以从中去读文件内容 |
String getName() | 返回请求参数的 name |
String getOriginalFilename() | 返回文件原本的文件名 |
long getSize() | 返回文件大小(单位字节) |
boolean isEmpty() | 判断上传的文件是否为空 |
void transferTo(File destination) | 将上传的文件保存到指定位置 |
如果从页面上同时上传多个文件,那么页面上的 file 可以使用同一个 name,而代码中则使用 CommonsMultipartFile 的数组类型的参数接受。数组中的每一个 MultipartFile 就代表着一个上传的文件。
<p><input type="file" name="files"></p>
<p><input type="file" name="files"></p>
<p><input type="file" name="files"></p>
@RequestParam("files") CommonsMultipartFile[] files
使用 Servlet 3.1 内置的文件上传功能
补充,其实 Servlet 3.0 就已经开始提供内置的上传功能,只不过该功能在 Servlet 3.1 中进一步增强/改进/完成。因此一般的说法是 Servlet 3.1 支持内置的文件上传功能。
利用 Servlet 3.1 实现文件上传的概念和使用过程和利用 commons-fileupload 本质上并无太大区别。只不过有几处小区别:
- 提供文件上传功能的是 StandardServletMultipartResolver ,不再是 CommonsMultipartResolver 。
spring-web.xml:
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>
- 对上传过程中的相关配置,是配置在
web.xml
中的 DispacherServlet 下,而非spring-web.xml
中的 MultipartResolver 下。
web.xml:
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>...</init-param>
<load-on-startup>...</load-on-startup>
<multipart-config>
<location>d:/</location> <!-- 临时文件的目录。该目录必须存在 -->
<max-file-size>2097152</max-file-size> <!-- 一次请求上传的单个文件最大2M -->
<max-request-size>4194304</max-request-size> <!-- 一次请求上传的多个文件整体大小不超过4M -->
</multipart-config>
</servlet>
- Controller 代码中使用的注解是 @RequestPart(“file”),而非 @RequestParam;绑定的参数类型是 MultipartFile,而不是 CommonsMultipartFile 。
@RequestMapping("/upload.do")
public String upload(String username,
String password,
@RequestPart("files") MultipartFile[] files)
MultipartFile 对象的功能与 CommonsMultipartFile 基本类似。
Spring MVC 中文件下载
Spring MVC 提供了一个 ResponseEntity 类型,使用它可以很方便地定义返回 HttpHeaders 和 HttpStatus ,以实现下载功能。
@RequestMapping("/download")
public ResponseEntity<byte[]> download(
HttpServletRequest req,
@RequestParam("filename") String filename,
Model model) throws Exception {
// 1. 准备一个字节数组(字节数组的内容来源于一个文件)。
// 这个字节数组,就是在本次 HTTP 请求中 Tomcat 要回给客户端浏览器的内容、数据。
String path = req.getServletContext().getRealPath("upload");
File file = new File(path + File.separator + filename);
byte[] bytes = FileUtils.readFileToByteArray(file);
// 2. 创建一个 ResponseEntity 对象。它代表着一个 HTTP 响应。
// 而一个 HTTP 响应又有行-头-体。其中『体』里存放的就是上述的代表文件内容的字节数组
HttpHeaders headers = new HttpHeaders();
// 解决文件名乱码问题
// String downloadFileName = new String(filename.getBytes("UTF-8"), "iso-8859-1");
// 第一个放在 header 中的键值对 attachment=xxx
headers.setContentDispositionFormData("attachment", filename);
// 第二个放在 header 中的键值对 media-type=xxxx
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(
bytes, // 响应体中携带的数据
headers, // 响应头
HttpStatus.OK); // 响应行中的状态码
return responseEntity;
}