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、創(chuàng)建一個上傳文件的頁面
下面創(chuàng)建一個 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、創(chuàng)建對應的 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 的就不要先存儲在磁盤中,直接存儲在內存中,然后從內存中再由程序來處理這個流,速度會更快一些,比如我們將文件上傳到阿里云,那么接收到流之后,無需存儲在臨時磁盤,而直接將這個流輸出到阿里云,這種中間就少了一個寫入本地磁盤的環(huán)境,效率會高很多,用內存換效率。
下面我們修改一下案例中的代碼,將 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 系列所有的案例代碼及文中的連接都會發(fā)布到這個倉庫中,大家可以關注起來。
將代碼拉到本地,本文案例位置

6、更多好文章
Spring 系列(共 56 篇) Java 高并發(fā)系列(共 34 篇) MySql 高手系列(共 27 篇) Maven 高手系列(共 10 篇) Mybatis 系列(共 12 篇) 聊聊 db 和緩存一致性常見的實現方式 接口冪等性這么重要,它是什么?怎么實現? 泛型,有點難度,會讓很多人懵逼,那是因為你沒有看這篇文章!
