之前的博客介紹過 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