1、单个文件上传
SpringMVC对文件的上传做了很好的封装,所以使用 SpringMVC 可以非常方便的实现文件上传。从 Spring3.1 开始,对于文件上传,提供了两个处理器:
- CommonsMultipartResolver:兼容性较好,可以兼容 Servlet3.0 之前的版本,但是它依赖了 commons-fileupload 这个第三方工具,所以如果使用这个,一定要添加 commons-fileupload 依赖。
- StandardServletMultipartResolver:兼容性较差,它适用于 Servlet3.0 之后的版本,它不依赖第三方工具,使用它,可以直接做文件上传。
本文使用 CommonsMultipartResolver 解析器来举例,并且以图片的上传的案例来介绍,下面是相关的具体操作:
[1]、导入文件上传相关的依赖包。
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
[2]、在 SpringMVC 的配置文件中配置 CommonsMultipartResolver 解析器:
注意:CommonsMultipartResolver 这个 Bean 一定要有 id,并且 id 必须是 multipartResolver
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 开启包扫描 -->
<context:component-scan base-package="com.thr"/>
<!-- 配置访问静态资源 -->
<mvc:resources mapping="/**" location="/"/>
<!-- 配置MVC注解驱动 -->
<mvc:annotation-driven/>
<!-- 注册视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置前缀 -->
<property name="prefix" value="/WEB-INF/pages/"/>
<!-- 配置后缀 -->
<property name="suffix" value=".jsp"/>
</bean>
<!-- 配置文件上传数据专用的解析器 -->
<!-- 这个bean的id必须是multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置文件编码格式 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 设置最大上传大小 -->
<property name="maxUploadSize" value="#{1024*1024}"/>
</bean>
</beans>
[3]、编写文件上传的页面
注意:上传文件的表单中有三点需要注意:
- 要点1:请求方式必须是POST
- 要点2:enctype属性必须是multipart/form-data , 以二进制的方式传输数据
- 要点3:文件上传框使用input标签,type属性设置为file
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<h1>单文件上传演示案例</h1>
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
请选择上传的图片:<input type="file" name="file"/><br/>
<input type="submit" value="上传单个文件"/>
</form>
</body>
</html>
[4]、编写文件上传的Controller。此时会用到MultipartFile这个接口,MultipartFile 封装了请求数据中的文件,此时这个文件存储在内存中或临时的磁盘文件中,需要将其转存到一个合适的位置,因为请求结束后临时存储将被清空。在 MultipartFile 接口中有如下方法:
方法名 | 返回值 | 描述 |
---|---|---|
getContentType() | String | 获取文件内容的类型 |
getOriginalFilename() | String | 获取文件的原名称 |
getName() | String | 获取form表单中文件组件的名字 |
getInputStream() | InputStream | 将文件内容以输入流的形式返回 |
getSize() | long | 获取文件的大小,单位为byte |
isEmpty() | boolean | 文件是否为空 |
transferTo(File dest) | void | 将数据保存到一个目标文件中 |
具体的Controller代码如下所示:
/**
* 单个文件的上传
*/
@Controller
public class UploadController {
@Autowired
private ServletContext servletContext;
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public String uploadFile(MultipartFile file, Model model, HttpServletRequest request) throws IOException {
// 获取图片文件名
String originalFilename = file.getOriginalFilename();
// 使用UUID给图片重命名,并且去掉UUID的四个"-"
String fileName = UUID.randomUUID().toString().replaceAll("-", "");
//获取图片的后缀
String extName = originalFilename.substring(originalFilename.lastIndexOf("."));
// 拼接新的图片名称
String newFileName = fileName + extName;
// 声明转存文件时,目标目录的虚拟路径(也就是浏览器访问服务器时,能够访问到这个目录的路径)
String virtualPath = "/images";
// 准备转存文件时,目标目录的路径。File对象要求这个路径是一个完整的物理路径。
// 而这个物理路径又会因为在服务器上部署运行时所在的系统环境不同而不同,所以不能写死。
// 需要调用servletContext对象的getRealPath()方法,将目录的虚拟路径转换为实际的物理路径
String targetDirPath = servletContext.getRealPath(virtualPath);
System.out.println("targetDirPath = " + targetDirPath);
// 创建File类型的对象用于文件转存
File filePath = new File(targetDirPath, newFileName);
if (!filePath.getParentFile().exists()) {
//如果文件夹不存在就新建一个
filePath.getParentFile().mkdirs();
}
// 调用transferTo()方法实现文件转存
file.transferTo(filePath);
// 为了让下一个页面能够将图片展示出来,将图片访问路径存入模型,把访问图片的路径准备好
String picPath = servletContext.getContextPath() + "/images/" + newFileName;
System.out.println("picPath = " + picPath);
model.addAttribute("picPath", picPath);
return "target";
}
}
[5]、编写上传成功的目标页面,用于显示图片。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>目标页面</title>
</head>
<body>
<h1>上传成功</h1>
<img src="${requestScope.picPath}" />
</body>
</html>
[6]、运行结果如下图所示:
2、多个文件上传
多个文件的上传就是在单个上传文件的基础上增加了一个循环的步骤,比较的简单,具体操作如下所示。
[1]、编写多个文件上传的页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<h1>多文件上传演示案例</h1>
<form action="${pageContext.request.contextPath}/uploadMore" method="post" enctype="multipart/form-data">
请选择上传的图片:<input type="file" name="files"/><br/>
请选择上传的图片:<input type="file" name="files"/><br/>
请选择上传的图片:<input type="file" name="files"/><br/>
<input type="submit" value="上传单个文件"/>
</form>
</body>
</html>
[2]、编写多个文件上传的Controller
/**
* 多个文件的上传
*/
@Controller
public class UploadController {
@Autowired
private ServletContext servletContext;
@RequestMapping(value = "/uploadMore", method = RequestMethod.POST)
public String uploadFileMore(@RequestParam("files") List<MultipartFile> fileList, Model model, HttpServletRequest request) throws IOException {
//用于存储发送至前台页面展示的路径
List<String> filePathNames = new ArrayList<>();
// 定义存储目标文件的目录
String targetDirPath = servletContext.getRealPath("/images");
System.out.println("targetDirPath = " + targetDirPath);
//循环遍历请求中的文件
for (MultipartFile file : fileList) {
// 获取图片文件名
String originalFilename = file.getOriginalFilename();
// 使用UUID给图片重命名,并且去掉UUID的四个"-"
String fileName = UUID.randomUUID().toString().replaceAll("-", "");
//获取图片的后缀
String extName = originalFilename.substring(originalFilename.lastIndexOf("."));
// 拼接新的图片名称
String newFileName = fileName + extName;
// 创建File类型的对象用于文件转存
File filePath = new File(targetDirPath, newFileName);
if (!filePath.getParentFile().exists()) {
//如果文件夹不存在就新建一个
filePath.getParentFile().mkdirs();
}
// 调用transferTo()方法实现文件转存
file.transferTo(filePath);
// 为了让下一个页面能够将图片展示出来,将图片访问路径存入模型,把访问图片的路径准备好
String picPath = servletContext.getContextPath() + "/images/" + newFileName;
System.out.println("picPath = " + picPath);
filePathNames.add(picPath);
}
if (filePathNames.size() > 0) {
model.addAttribute("filePathNames", filePathNames);
}
return "targetmore";
}
}
[3]、上传成功后展示图片的页面
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>目标页面</title>
</head>
<body>
<h1>上传成功</h1>
<c:forEach items="${filePathNames}" var="fileName">
<img src="${fileName}" />
</c:forEach>
</body>
</html>
[4]、运行结果如下图所示:
3、关于已上传文件的保存问题
[1]、保存到Tomcat服务器上的缺陷
- 当用户上传文件非常多的时候,拖慢Tomcat运行的速度
- 当Web应用重新部署时,会导致用户以前上传的文件丢失
- 当Tomcat服务器以集群的形式运行时,文件不会自动同步
[2]、建议的解决方案
- 自己搭建文件服务器,例如:FastDFS等
- 第三方文件服务
- 阿里云提供的OSS对象存储服务
- 七牛云
- ……