一、前提条件

1、表单的要求

  • method=”post”
  • 编码方式 enctype=”multipart/form-data”
  • 文件上传框 input type=”file”
1
2
3
<form action="..." method="post" enctype="multipart/form-data">
<input type="file" ... />
</form>

2、额外导入的 jar 包

在 Spring MVC 的 jar 包基础上导入

commons-fileupload-1.3.jar

commons-io-2.0.1.jar

3、配置 CommonsMultipartResolver

1
2
3
4
5
<!-- 注意:bean id必须是multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 避免中文乱码 -->
<property name="defaultEncoding" value="UTF-8"/>
</bean>

4、接收数据

① 普通数据:和以前一样正常接收

② 文件数据:使用@RequestParam 注解注入到 MultipartFile 类型的入参中

1
2
3
4
@RequestMapping("/upload")
public String upload(@RequestParam("picture") MultipartFile picture) {
return "...";
}

Alt+7 查看 MultiPartFile 类型里面的方法,看看通过这个接口我们可以得到哪些数据

二、实验测试

  • 配置 spring-mvc 的配置文件
  • 配置 web.xml 配置前端控制器
  • 配置 index.jsp 页面
  • 配置控制器 UploadController 类

1、配置 spring-mvc.xml 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.atguigu.upload.controller"/>

<!-- 标配 -->
<mvc:annotation-driven/>

<!-- 配置视图解析器 -->
<mvc:default-servlet-handler/>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="viewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>

<!-- 配置文件上传数据专用的解析器 -->
<!-- 这个bean的id必须是multipartResolver。在Spring环境下,还有很多bean对id有精确要求,此时可以查看这个类实现的接口中和主要功能相关的那个接口,这个接口名首字母小写就是bean的id -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

<!-- 指定默认字符集 -->
<property name="defaultEncoding" value="UTF-8"/>

</bean>

2、web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

3、index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
<title>文件上传</title>
</head>
<body>
<!-- 上传文件的表单有三点需要注意: -->
<!-- 要点1:请求方式必须是post -->
<!-- 要点2:enctype属性必须是multipart/form-data -->
<!-- 要点3:文件上传框使用input标签,type属性设置为file -->
<form
action="${pageContext.request.contextPath}/upload"
method="post"
enctype="multipart/form-data"
>
用户名:<input type="text" name="userName" /><br />
头像:<input type="file" name="headPicture" /><br />

<button type="submit">保存</button>
</form>
</body>
</html>

4、UploadController

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
@Controller
public class UploadController {

@Autowired
private ServletContext servletContext;

@RequestMapping("/upload")
public String doUpload(
// 正常获取普通表单项
@RequestParam("userName") String userName,

// 使用MultipartFile类型获取文件上传表单项
@RequestParam("headPicture")MultipartFile headPicture,

Model model
) throws IOException {

System.out.println("userName = " + userName);

// 获取上传文件的input标签的name属性值
String inputName = headPicture.getName();
System.out.println("inputName = " + inputName);

// 获取原始文件名
String originalFilename = headPicture.getOriginalFilename();
System.out.println("originalFilename = " + originalFilename);

// 根据原始文件名获取文件扩展名,包含.
String extName = originalFilename.substring(originalFilename.lastIndexOf("."));

// 在服务器端生成新的文件名(避免服务器端接收到同名文件把之前保存的文件给覆盖掉)
String fileName = System.currentTimeMillis() +extName;
System.out.println("fileName = " + fileName);

// 获取已上传文件的内容类型
String contentType = headPicture.getContentType();
System.out.println("contentType = " + contentType);

// 获取已上传文件是否为空,空的话为true
boolean empty = headPicture.isEmpty();
System.out.println("empty = " + empty);

// 获取已上传文件的大小
long size = headPicture.getSize();
System.out.println("size = " + size);

// 获取已上传文件的字节数组
byte[] bytes = headPicture.getBytes();
System.out.println("bytes = " + Arrays.asList(bytes));

// 获取已上传文件的输入流
InputStream inputStream = headPicture.getInputStream();
System.out.println("inputStream = " + inputStream);

// 声明转存文件时,目标目录的虚拟路径(也就是浏览器访问服务器时,能够访问到这个目录的路径,可以放到web根目录下)
String virtualPath = "/hello";

// 准备转存文件时,目标目录的路径。File对象要求这个路径是一个完整的物理路径。
// 而这个物理路径又会因为在服务器上部署运行时所在的系统环境不同而不同,所以不能写死。
// 需要调用servletContext对象的getRealPath()方法,将目录的虚拟路径转换为实际的物理路径
String targetDirPath = servletContext.getRealPath(virtualPath);

// 创建File类型的对象用于文件转存
File targetFile = new File(targetDirPath + "/" + fileName);

// 调用transferTo()方法实现文件转存
headPicture.transferTo(targetFile);

// 为了让下一个页面能够将图片展示出来,将图片访问路径存入模型
// 把访问图片的路径准备好
String picPath = servletContext.getContextPath() + "/hello/" + fileName;

model.addAttribute("picPath", picPath);

return "target";
}

三、扩展

1、上传请求诡异重定向问题

images

当 Web 应用中某个目录的访问路径和@RequestMapping 匹配的路径雷同时,会导致无法正常访问。所以为了避免这种情况,一定要让各种资源独立命名,有各自不同的访问地址。

2、关于已上传文件的保存

① 保存到 Tomcat 服务器上的缺陷

  • 当用户上传文件非常多的时候,拖慢 Tomcat 运行的速度
  • 当 Web 应用重新部署时,会导致用户以前上传的文件丢失
  • 当 Tomcat 服务器以集群的形式运行时,文件不会自动同步

② 建议的解决方案

  • 自己搭建文件服务器,例如:FastDFS 等
  • 第三方文件服务

3、同时上传多个文件

① 表单

1
2
3
4
5
6
7
8
9
<form
action="${pageContext.request.contextPath}/upload/multi/file"
method="post"
enctype="multipart/form-data"
>
文件1:<input type="file" name="picture" /><br />
文件2:<input type="file" name="picture" /><br />
<button type="submit">保存</button>
</form>

②Java 代码

使用 List<MultipartFile>类型接收即可

1
2
3
4
5
6
7
8
9
10
@RequestMapping("/upload/multi/file")
public String multiFile(@RequestParam("picture")List<MultipartFile> fileList) {

for (MultipartFile multipartFile : fileList) {
String originalFilename = multipartFile.getOriginalFilename();
System.out.println("originalFilename = " + originalFilename);
}

return "target";
}