SpringMvc 文件上传开发总结
之前的博客介绍过 Servlet 的文件上传和下载,对于文件下载来说,SpringMvc 跟 Servlet 的实现方式是一样,这里就不再介绍了。对于文件上传来说,虽然 SpringMvc 底层实现方式跟 Servlet 也是一样的,都是使用第三方 commons-fileupload 的 jar 包组件来实现,但是 SpringMvc 隐藏了实现细节,简化了代码编写量,大大提高了开发效率,使用起来非常方便。
本篇博客从实际开发场景出发,通过表单上传和 ajax 异步上传两种代码实现方式进行演示,在博客最后会提供源代码下载。
一、搭建工程
新建一个 maven 项目,导入相关 jar 包,我所导入的 jar 包都是最新的,内容如下:
有关具体的 jar 包地址,可以在 https://mvnrepository.com 上进行查询。
<dependencies>
<!--导入 servlet 相关的 jar 包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<!--导入 Spring 核心 jar 包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
<!--导入 SpringMvc 的 jar 包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
<!--导入 jackson 相关的 jar 包-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.1</version>
</dependency>
<!--文件上传组件支持-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
<build>
<!--设置插件-->
<plugins>
<!--具体的插件配置-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
需要注意的是:我们需要导入第三方的 commons-fileupload 文件上传组件,SpringMvc 底层也是使用它来完成文件上传的。
这次我们使用 tomcat 插件来运行网站进行测试,因为这样可以直接在 IDEA 中看到实际运行测试的结果。
有关 tomcat 插件,可以到官网查询,地址为:https://tomcat.apache.org/maven-plugin.html
目前 tomcat 官网的插件,最新版本也就是 tomcat7 的 2.2 版本。
配置好引用的 jar 包后,打开右侧的 Maven 窗口,刷新一下,这样 Maven 会自动下载所需的 jar 包文件。
搭建好的项目工程整体目录比较简单,具体如下图所示:
com.jobs.config 包下存储的是 SpringMvc 的配置文件和 Servlet 的初始化文件
com.jobs.controller 包下存储的是用于提供 api 接口的类
com.jobs.exception包下存储的是全局异常捕获和处理类
web 目录下放置的是网站文件,只有一个静态页面和一些 js 文件
二、SpringMvc 关键配置
有关 ServletInitConfig 和 SpringMvcConfig 的具体内容,跟之前发布的博客相比,重复性内容太多了,这里仅仅介绍关键的配置信息。在 SpringMvcConfig 类中增加 SpringMvc 装载文件上传组件 Bean 的获取方法,具体内容为:
//配置 SpringMvc 上传文件的解析器相关参数,底层依赖于第三方 commons-fileupload 的 jar 包组件
//需要注意的是:这里的上传组件的 Bean 必须取名为 multipartResolver
@Bean("multipartResolver")
public CommonsMultipartResolver getMultipartResolver() {
CommonsMultipartResolver cmr = new CommonsMultipartResolver();
cmr.setDefaultEncoding("UTF-8");
//设置单个文件大小限制(byte),这里设置为 4M
cmr.setMaxUploadSizePerFile(4 * 1024 * 1024);
//设置整个请求,最大允许上传的文件总大小限制(byte),这里设置为 10M
cmr.setMaxUploadSize(10 * 1024 * 1024);
//设置上传文件过程中,内存最多使用 10M
cmr.setMaxInMemorySize(10 * 1024 * 1024);
return cmr;
}
需要注意的是:Bean 的名称必须为 multipartResolver ,否则文件无法上传成功,具体原因可以查看 SpringMvc 的源码。
另外我们还需要有一个全局的异常捕获和处理的类,方便在文件上传的过程中,如果出现问题,不至于显示默认的报错页面。
package com.jobs.exception;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Component
@ControllerAdvice
public class ExceptionAdvice {
//全局捕获所有异常,并进行处理
@ExceptionHandler(Exception.class)
public String handException(Exception ex, Model m) {
//将异常信息,记录到日志中
System.out.println("捕获到的异常信息:" + ex);
//给用户展示友好的信息,隐藏具体的问题细节
m.addAttribute("msg", ex.getMessage());
return "fail";
}
}
这里只编写了一个异常处理方法,处理所有的异常。当然这里只是 demo 演示,当捕获到异常时,跳转到 fail.jsp 页面展示错误信息。在实际开发场景中,你可以进行一些人性化的处理和信息提示。错误日志肯定是需要记录的,方便排查和解决问题。
三、上传文件的代码细节
下面展示 Controller 中接收表单上传文件和 ajax 异步提交上传文件的方法,因为上传文件需要进行 Post 提交,因此这两个方法直接加上了 @PostMapping 注解,表示仅仅接收 Post 请求,具体内容如下:
package com.jobs.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
@RequestMapping("/test")
@Controller
public class TestController {
//通过 form 表单上传 2 个文件,分别用参数 file1 和 file2 接收
@PostMapping("/formUpload")
public String formUploadFile(HttpServletRequest request,
String remark,
MultipartFile file1,
MultipartFile file2) throws IOException {
System.out.println("提交过来的文件备注信息为:" + remark);
/*
//MultipartFile参数中封装了上传的文件的相关信息
//获取上传文件的大小(单位:字节)
System.out.println(file.getSize());
//获取上传文件的字节数组
System.out.println(file.getBytes());
//获取上传文件的 mime 类型
System.out.println(file.getContentType());
//这个是参数名称,一般不会使用
System.out.println(file.getName());
//获取原始文件名称
System.out.println(file.getOriginalFilename());
//判断文件是否为空文件
System.out.println(file.isEmpty());
*/
//设置上传后的文件保存的文件夹,如果不存在,就自动创建
String dir = request.getServletContext().getRealPath("/upload");
File directory = new File(dir);
if (!directory.exists()) {
directory.mkdirs();
}
//如果文件不为空的话,就写入磁盘中
if (file1 != null && !file1.isEmpty()) {
//这里要求验证单个上传文件,不允许大于 2M
long size = file1.getSize();
if (size > 1 * 1024 * 1024) {
request.setAttribute("msg", "file1上传的文件大于2M");
return "fail";
}
//获取所上传文件的原始文件名
//你也可以生成一个随机的文件名进行保存
//这里的 demo 为了演示,就直接使用原始文件名进行写入磁盘了
String fname = file1.getOriginalFilename();
//将文件写入到磁盘中
file1.transferTo(new File(dir, fname));
}
if (file2 != null && !file2.isEmpty()) {
//这里要求验证单个上传文件,不允许大于 2M
long size = file2.getSize();
if (size > 1 * 1024 * 1024) {
request.setAttribute("msg", "file2上传的文件大于2M");
return "fail";
}
String fname = file2.getOriginalFilename();
file2.transferTo(new File(dir, fname));
}
return "success";
}
//通过 ajax 表单上传 2 个文件,分别用参数 file1 和 file2 接收
@PostMapping("/ajaxUpload")
@ResponseBody
public String ajaxUploadFile(HttpServletRequest request,
String remark,
MultipartFile file1,
MultipartFile file2) throws IOException {
System.out.println("提交过来的文件备注信息为:" + remark);
//设置上传后的文件保存的文件夹,如果不存在,就自动创建
String dir = request.getServletContext().getRealPath("/upload");
File directory = new File(dir);
if (!directory.exists()) {
directory.mkdirs();
}
//如果文件不为空的话,就写入磁盘中
if (file1 != null && !file1.isEmpty()) {
//这里要求验证单个上传文件,不允许大于 2M
long size = file1.getSize();
if (size > 1 * 1024 * 1024) {
return "file1上传的文件大于2M";
}
//获取所上传文件的原始文件名
//你也可以生成一个随机的文件名进行保存
//这里的 demo 为了演示,就直接使用原始文件名进行写入磁盘了
String fname = file1.getOriginalFilename();
//将文件写入到磁盘中
file1.transferTo(new File(dir, fname));
}
if (file2 != null && !file2.isEmpty()) {
//这里要求验证单个上传文件,不允许大于 2M
long size = file2.getSize();
if (size > 1 * 1024 * 1024) {
return "file2上传的文件大于2M";
}
String fname = file2.getOriginalFilename();
file2.transferTo(new File(dir, fname));
}
return "ajax上传文件成功";
}
}
从上面的代码可以看出,SpringMvc 仅仅需要一行代码(使用 MultipartFile 的 transferTo 方法)就实现了文件上传,非常简单。
配合测试文件上传的页面,就是本 Demo 网站的主页 index.html ,具体内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SpringMvc上传文件演示</title>
</head>
<body>
<fieldset>
<legend>Form 表单上传文件</legend>
<form action="/test/formUpload" method="post" enctype="multipart/form-data">
文件信息备注:<input type="text" name="remark"><br/>
上传文件1:<input type="file" name="file1"><br/>
上传文件2:<input type="file" name="file2"><br/>
<input type="submit" value="上传">
</form>
</fieldset>
<hr/>
<fieldset>
<legend>Ajax 异步上传文件</legend>
文件信息备注:<input type="text" id="myRemark"><br/>
上传文件1:<input type="file" id="myFile1"><br/>
上传文件2:<input type="file" id="myFile2"><br/>
<input type="button" value="上传" id="btnUpload">
</fieldset>
<script src="./js/jquery-3.6.0.min.js"></script>
<script src="./js/upload.js"></script>
</body>
</html>
需要注意的是:对于 Form 表单上传文件的方式,其必须要有 enctype="multipart/form-data" 这个属性。
下面列出 ajax 上传文件的 js 内容:
$(function () {
$('#btnUpload').click(function () {
//获取 remark 信息
var remark = $('#myRemark').val();
//获取第一个文件
var f1 = $('#myFile1')[0].files[0];
//获取第二个文件
var f2 = $('#myFile2')[0].files[0];
//采用 FormData 组织数据
var formdata = new FormData();
formdata.append("remark", remark);
formdata.append("file1", f1);
formdata.append("file2", f2);
$.ajax({
type: "post",
url: "/test/ajaxUpload",
dataType: "json",
data: formdata,
cache: false, //上传文件无需缓存
processData: false, //使数据不做处理
contentType: false, //不要设置Content-Type请求头
success: function (data) {
alert("返回的数据:" + data);
}
});
});
})
需要注意的是:ajax 上传文件,采用的是 FormData 进行组织数据,在提交的过程中,processData 和 contentType 必须设置为 false 。
到此为止,有关 SpringMvc 通过表单同步提交上传文件和通过 ajax 异步提交上传文件的代码实现方式已经介绍完毕。
本博客 Demo 的源代码下载地址为:https://files.cnblogs.com/files/blogs/699532/SpringMvc_FileUpload.zip