Servlet3.0新特性:文件上传,真的太好用了!!!
Servlet3.0 简化了文件上传,用起来相当方便,需要用到一个注解@MultipartConfig
。
1、依赖软件及版本
jdk1.8 maven3.6.1 tomcat10
2、@MultipartConfig
将其标注在 servlet 上面,表示这个 servlet 可以处理 Multipart 方式提交的数据,也就是支持文件上传,先来看下这个注解的一些属性
属性名称 | 类型 | 是否必须 | 说明 |
---|---|---|---|
location | String | 否 | servlet 接受到文件的时候,会先将其保存在一个临时目录,这个参数就可以指定这个目录的位置 |
maxFileSize | long | 否 | 允许上传的文件最大值,默认为-1,表示没有限制 |
maxRequestSize | long | 否 | 针对 multipart/form-data 请求的最大数量,默认为-1,表示没有限制 |
fileSizeThershold | int | 否 | 文件大小超过这个值的会被写入磁盘,默认值为 0,都会写入磁盘,小于这个值的会被写在内存中 |
3、多文件上传案例
3.1、创建一个上传文件的页面
下面创建一个 jsp 页面,包含 3 个元素,一个普通的输入框,2 个文件元素
<%@ page language="java" pageEncoding="UTF-8" %>
<html lang="en">
<meta charset="UTF-8">
servlet3.0上传多个文件
<form enctype="multipart/form-data" method="post" action="${pageContext.request.contextPath}/uploadFile">
description:<input name="description"></input name=
file1:<input type="file" name="file1"></input type=
file2:<input type="file" name="file2"></input type=
<input type="submit" value="多文件上传"></input type=
</form enctype=</meta charset=</html lang=
3.2、创建对应的 servlet
代码如下,需在 servlet 上添加@MultipartConfig
注解,有了这个注解之后,当请求进入到 servlet 的 service 方法中时,请求中每个部分都被处理好了,表单中的每个元素被放在了Part
对象中,调用request.getParts()
可以获取提交的表单中的元素列表,其中包含了普通的元素和文件元素,需要根据 Part 对象判断是文件还是普通元素,如果是文件则调用 part.write 方法并传入文件保存的地址,则将上传的文件保存到目标位置。
package com.javacode2018.springboot.lesson001.demo2;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
import java.io.IOException;
import java.util.Collection;
@WebServlet(urlPatterns = "/uploadFile")
@MultipartConfig(
location = "", //存放生成文件的地址
maxFileSize = -1,//允许上传的文件最大值,默认为-1,表示没有限制
maxRequestSize = -1,//针对 multipart/form-data 请求的最大数量,默认为-1,表示没有限制
fileSizeThreshold = 0// 文件大小超过这个值的会被写入磁盘,默认值为0,都会写入磁盘,小于这个值的会被写在内存中
)
public class UploadFileServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//①、获取请求中的参数列表
Collection parts = req.getParts();
//②、遍历参数
for (Part part : parts) {
//③、获取文件名称,文件名称不为空的表示是文件
String fileName = part.getSubmittedFileName();
if (fileName != null) {
String saveFilePath = req.getServletContext().getRealPath("/") + fileName;
//④、调用 part.write 将上传的文件写入目标文件
part.write(saveFilePath);
System.out.println(String.format("文件 [%s] 上传成功,保存位置:[%s]", fileName, saveFilePath));
} else {
System.out.println(String.format("非文件参数:[%s->%s]", part.getName(), req.getParameter(part.getName())));
}
}
}
}
3.3、运行
提交表单,控制台输出
上面代码中我们调用了part.write
方法将上传的文件保存到目标位置,也可以通过流的方式来保存文件,可以调用part.getInputStream()
获取上传的文件流,然后自己去保存,如下
private void saveFile(Part part, String filePath) throws IOException {
InputStream inputStream = part.getInputStream();
try (FileOutputStream fileOutputStream = new FileOutputStream(new File(filePath))) {
byte[] bs = new byte[4048];
int i = 0;
while ((i = inputStream.read(bs)) != -1) {
fileOutputStream.write(bs, 0, i);
}
}
}
4、fileSizeThershold 属性
当上传的文件大于这个值的时候,文件会被写入到磁盘,默认值是 0,所以默认情况下,上传的文件都会被先保存在磁盘中,代码中设置个断点来看一下
再次运行案例,走到断点的地方,看下面,可以看到 part 中有个 location 属性,这个就是上传的文件临时存储的位置。
有时候,我们希望上传的文件比如小于 10M 的就不要先存储在磁盘中,直接存储在内存中,然后从内存中再由程序来处理这个流,速度会更快一些,比如我们将文件上传到阿里云,那么接收到流之后,无需存储在临时磁盘,而直接将这个流输出到阿里云,这种中间就少了一个写入本地磁盘的环境,效率会高很多,用内存换效率。
下面我们修改一下案例中的代码,将 fileSizeThershold(byte)的值设置为 10MB,如下
service 方法中输出一下文件的信息(大小、文件流类型),通过文件流类型可以判断文件是在内存中还是在磁盘中,如下图,加入下面部分代码
System.out.println(String.format("文件 [%s],大小[%s byte],文件流类型:[%s]",
fileName,
part.getSize(),
part.getInputStream().getClass()));
再次运行,这次,我们选一个小于 10M 和一个大于 10M 的,运行输出
非文件参数:[description->哈哈哈]
文件 [MySQL_5.1_zh.chm],大小[2661409 byte],文件流类型:[class java.io.ByteArrayInputStream]
文件 [MySQL_5.1_zh.chm] 上传成功,保存位置:[E:\idea\springboot-series\lesson-001-servlet3.0\target\lesson-001-servlet3.0-1.0-SNAPSHOT\MySQL_5.1_zh.chm]
文件 [C和指针.pdf],大小[29777269 byte],文件流类型:[class java.io.FileInputStream]
文件 [C和指针.pdf] 上传成功,保存位置:[E:\idea\springboot-series\lesson-001-servlet3.0\target\lesson-001-servlet3.0-1.0-SNAPSHOT\C和指针.pdf]
注意看日志中文件大小和流的类型,第 2 条和第 4 条日志,可以看出来第一个文件小于 10MB,流类型是ByteArrayInputStream
,说明被存储在内存中,而第二个文件大于 10MB 了,被存储在磁盘中了,所以类型是 FileInputStream。
5、案例代码
https://gitee.com/javacode2018/springboot-series
整个 springboot 系列所有的案例代码及文中的连接都会发布到这个仓库中,大家可以关注起来。
将代码拉到本地,本文案例位置
6、更多好文章
Spring 系列(共 56 篇) Java 高并发系列(共 34 篇) MySql 高手系列(共 27 篇) Maven 高手系列(共 10 篇) Mybatis 系列(共 12 篇) 聊聊 db 和缓存一致性常见的实现方式 接口幂等性这么重要,它是什么?怎么实现? 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!