文件上传
# 90.文件上传
下面我们来实现一个非常常用的功能:文件上传
# 文件上传的必要前提
- form 表单的 enctype 取值必须是:multipart/form-data,enctype: 是表单请求正文的类型,默认值是: application/x-www-form-urlencoded
- method 属性取值必须是 Post
- 提供一个文件选择域
# 文件上传的原理分析
当 form 表单的 enctype 取值为默认值,也就是 enctype=application/x-www-form-urlencoded
时,form 表单的正文内容是一组键值对:key = value&key = value&key = value
当 form 表单的 enctype 取值为 Mutilpart/form-data,不是默认值时,request.getParameter()
将失效,请求正文内容就变成:
-----------------------------7de1a433602ac 分界符
Content-Disposition: form-data; name="userName" 协议头
aaa 协议的正文
-----------------------------7de1a433602ac
Content-Disposition: form-data; name="file";
filename="C:\Users\Desktop\fileupload_demofile\b.txt"
Content-Type: text/plain 协议的类型(MIME 类型)
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
-----------------------------7de1a433602ac--
2
3
4
5
6
7
8
9
每一部分都是 MIME 类型描述的正文。此时我们得自己去解析这个正文内容。
# 借助第三方组件实现文件上传
我们可以用第三方工具帮我们解析,使用 Apache 提供的 Commons-fileupload 组件实现文件上传,需要导入该组件相应的支撑 jar 包:Commons-fileupload 和 commons-io。该工具也是用的很多的
commons-io 不属于文件上传组件的开发 jar 文件,但 Commons-fileupload 组件从 1.1 版本开始,工作时需要 commons-io 包的支持。
# 搭建环境
为了不收之前的文件影响,我们删除所有的 JSP 页面 和 所有的 Java 代码,而配置文件我们可以留着不动
新建 JSP:我们重新新建一个 index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>文件上传</h3>
</body>
</html>
2
3
4
5
6
7
8
9
新建 controller
package com.peterjxl.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/fileUpload1")
public String fileUpload1() {
System.out.println("fileUpload1");
return "success";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
新建 success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>上传文件成功</h3>
</body>
</html>
2
3
4
5
6
7
8
9
# 新建表单
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>文件上传</h3>
<form action="/user/fileUpload1" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"/> <br/>
<input type="submit" value="上传"/>
</form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
# 借住 fileupload 组件实现上传
我们引用依赖:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
前端提交的东西,最后都会封装到 request 中,所以我们首先使用 Servlet 原生的 API:
public String fileUpload1(HttpServletRequest request)
然后我们定义文件上传后,要存放的文件夹是否存在,不存在则创建(这里以 upload 文件夹为例)
String realPath = request.getSession().getServletContext().getRealPath("/uploads/");
// 判断路径是否存在
File file = new File(realPath);
if (! file.exists()) {
// 创建该文件夹
file.mkdirs();
}
2
3
4
5
6
7
8
下一步就是解析 request 对象,获取到上传的文件,然后写入到磁盘里了,具体代码如下:
// 解析request对象,获取上传文件项
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
// 解析request
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items){
// 进行判断,当前item对象是否是上传文件项,如果不是则说明是表单内容
if (item.isFormField()){
// 是表单项
}else {
// 是上传文件项
// 获取上传文件的名称
String name = item.getName();
// 完成文件上传
item.write(new File(realPath, name));
// 删除临时文件。当文件小于10kb,则文件是在内存中的;如果大于10kb,会有一个临时文件。
item.delete();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
我们重启并测试,可以看到上传文件后,能在项目的路径里找到该文件。关于该文件具体的路径,是以部署后的为准,每个人的路径可能不一样,可以看看 IDE 里的配置。
# 生成唯一的文件名
有时候我们可能会上传同名的文件,为了不覆盖,可以生成唯一的文件名,例如使用 UUID:
String name = item.getName();
String uuid = UUID.randomUUID().toString().replace("-", "");// 防止文件名重复
name = uuid + "_" + name;
item.write(new File(realPath, name));
2
3
4
# SpringMVC 实现文件上传
接下来我们演示下如何使用 SpringMVC 上传文件。比起传统的方式,SpringMVC 实现起来更简单
# 原理
SpringMVC 框架提供了 MultipartFile 对象,该对象表示上传的文件,要求变量名称必须和表单 file 标签的 name 属性名称相同
当我们上传文件时,request 对象首先会交给前端控制器:
而 SpringMVC 是有很多组件的,其中有一个是文件解析器,当我们配置后,前端控制器就会去找该组件,负责解析 request 请求,然后拿到上传的文件项
然后我们继续执行 controller 的方法,假设我们新增一个方法 fileupload2,然后我们可以通过参数绑定的方式,传入到该方法中。传入的是一个 MultipartFile 对象:
注意点:要求变量名称必须和表单 file 标签的 name 属性名称相同。
# 配置文件解析器
CommonsMultipartResolver
还允许我们做一些配置:
maxUploadSize
:上传的最大文件大小,字节为单位。例如限制为 10M,则 10 * 1024 * 1024 = 10485760
我们在 springmvc.xml 中配置:
<!-- 配置文件解析器对象,要求id名称必须是multipartResolver -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/>
</bean>
2
3
4
5
# 新增方法
@RequestMapping("/fileUpload2")
public String fileUpload2(HttpServletRequest request, MultipartFile upload) throws IOException {
System.out.println("fileUpload2");
String realPath = request.getSession().getServletContext().getRealPath("/uploads/");
System.out.println("realPath: " + realPath);
File file = new File(realPath);
if (! file.exists()) {
// 创建该文件夹
file.mkdirs();
}
String originalFilename = upload.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replace("-", "");// 防止文件名重复,这里我们将减号替换为空字符串
originalFilename = uuid + "_" + originalFilename;
upload.transferTo(new File(realPath, originalFilename));
return "success";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
注意方法中 MultipartFile 的参数名,必须和表单中的 file 属性名字一样,这里是 upload。最后我们也不用删除临时文件,SpringMVC 会帮我们删除
# 新增表单
<h3>SpringMVC文件上传</h3>
<form action="/user/fileUpload2" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"/> <br/>
<input type="submit" value="上传"/>
</form>
2
3
4
5
6
# 源码
本项目已将源码上传到 GitHub (opens new window) 和 Gitee (opens new window) 上。并且创建了分支 demo8,读者可以通过切换分支来查看本文的示例代码。