Spring MVC 处理文件上传主要依赖于 MultipartResolver
接口及其实现。最常用的实现是 CommonsMultipartResolver
(基于 Apache Commons FileUpload)和 StandardServletMultipartResolver
(基于 Servlet 3.0+ API)。
以下是如何配置和使用文件上传功能的步骤:
需要添加相应的依赖到你pom.xml
(Maven) 或 build.gradle
(Gradle) 文件中。
a) 使用 Apache Commons FileUpload (推荐,功能更全面):
如果想使用 CommonsMultipartResolver
,需要添加以下依赖:
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.5version>
dependency>
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>2.11.0version>
dependency>
b) 使用 Servlet 3.0+ API (无需额外依赖,如果你的 Servlet 容器支持 Servlet 3.0+):
如果你的 Servlet 容器 (如 Tomcat 7+, Jetty 8+) 支持 Servlet 3.0 API,你可以使用 StandardServletMultipartResolver
。这种情况下,通常不需要显式添加额外的 commons-fileupload
依赖。Spring Boot 默认会优先使用 Servlet 3.0+ 的方式,如果 commons-fileupload
不在 classpath 中。
你需要在 Spring 的配置文件中注册一个 MultipartResolver
bean。
a) Java 配置 (推荐):
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver; // For Commons FileUpload
// import org.springframework.web.multipart.support.StandardServletMultipartResolver; // For Servlet 3.0+
@Configuration
public class WebConfig { // 或者你的 Spring MVC 配置类
// 使用 CommonsMultipartResolver
@Bean(name = "multipartResolver") // bean 的名字必须是 "multipartResolver"
public MultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(10485760); // 10MB,设置总上传数据总大小
multipartResolver.setMaxUploadSizePerFile(5242880); // 5MB,设置单个文件大小 (需要 Servlet 3.0+ 支持或自定义逻辑)
multipartResolver.setMaxInMemorySize(1048576); // 1MB,超过该大小会写入临时文件
multipartResolver.setDefaultEncoding("UTF-8");
// multipartResolver.setUploadTempDir(new FileSystemResource("/tmp/uploads")); // 可选,设置临时文件目录
return multipartResolver;
}
// 或者,使用 StandardServletMultipartResolver (需要 Servlet 3.0+ 环境)
/*
@Bean
public StandardServletMultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
*/
}
注意:
multipartResolver
。setMaxUploadSizePerFile
是 Spring 5.0 中为 CommonsMultipartResolver
添加的,底层依赖 Servlet 3.0 的 Part.getSize()
。对于更早版本或更细致的控制,可能需要在控制器中检查。StandardServletMultipartResolver
,文件大小限制通常在 web.xml
或通过 Servlet 3.0 的 MultipartConfigElement
以编程方式配置(例如在 ServletContainerInitializer
或 Spring Boot 的 application.properties
中)。Spring Boot 用户:
如果你使用 Spring Boot,它会自动配置 MultipartResolver
。
commons-fileupload
在 classpath 中,Boot 会自动配置 CommonsMultipartResolver
。StandardServletMultipartResolver
(如果 Servlet 3.0+ 可用)。application.properties
或 application.yml
中配置相关属性:# application.properties
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=5MB # 单个文件最大值
spring.servlet.multipart.max-request-size=10MB # 请求总大小最大值
spring.servlet.multipart.file-size-threshold=1MB # 文件写入磁盘的阈值
# spring.servlet.multipart.location=/tmp/uploads # 临时文件存储位置
b) XML 配置 (传统方式):
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/>
<property name="maxUploadSizePerFile" value="5242880"/>
<property name="maxInMemorySize" value="1048576"/>
<property name="defaultEncoding" value="UTF-8"/>
bean>
在你的 JSP 或 HTML 页面中,确保表单的 method
是 POST
,并且 enctype
设置为 multipart/form-data
。
DOCTYPE html>
<html>
<head>
<title>File Uploadtitle>
head>
<body>
<h2>Upload Single File:h2>
<form method="POST" action="/your-app-context/uploadSingleFile" enctype="multipart/form-data">
File: <input type="file" name="file" /> <br/><br/>
Description: <input type="text" name="description" /> <br/><br/>
<input type="submit" value="Upload" />
form>
<h2>Upload Multiple Files:h2>
<form method="POST" action="/your-app-context/uploadMultipleFiles" enctype="multipart/form-data">
File 1: <input type="file" name="files" /> <br/><br/>
File 2: <input type="file" name="files" /> <br/><br/>
Description: <input type="text" name="description" /> <br/><br/>
<input type="submit" value="Upload" />
form>
body>
html>
注意:
name="file"
和 name="files"
中的 name
属性值需要与 Controller 中 @RequestParam
的值匹配。/your-app-context/
是你的应用上下文路径,例如 /my-app/
。在 Controller 中,你可以使用 @RequestParam
注解将上传的文件绑定到 MultipartFile
类型的参数。
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@Controller
public class FileUploadController {
// 你可以从配置文件读取这个路径
private static final String UPLOAD_DIR = "/path/to/your/upload/directory/"; // 请替换为实际路径
@PostMapping("/uploadSingleFile")
public String uploadSingleFile(@RequestParam("file") MultipartFile file,
@RequestParam("description") String description,
RedirectAttributes redirectAttributes) {
if (file.isEmpty()) {
redirectAttributes.addFlashAttribute("message", "Please select a file to upload.");
return "redirect:/uploadStatus"; // 或返回上传页面
}
try {
// 确保上传目录存在
File uploadDir = new File(UPLOAD_DIR);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 获取文件名和扩展名
String originalFileName = file.getOriginalFilename();
String fileExtension = "";
if (originalFileName != null && originalFileName.lastIndexOf(".") != -1) {
fileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
}
// 生成新的唯一文件名,防止覆盖
String newFileName = UUID.randomUUID().toString() + fileExtension;
// 构建文件保存路径
Path path = Paths.get(UPLOAD_DIR + newFileName);
// 保存文件
// 方法1: 使用 MultipartFile.transferTo()
file.transferTo(path.toFile());
// 方法2: 使用 Java NIO Files.copy (更灵活)
// Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
System.out.println("Description: " + description);
System.out.println("Uploaded file: " + originalFileName + " as " + newFileName);
redirectAttributes.addFlashAttribute("message",
"You successfully uploaded '" + originalFileName + "' as '" + newFileName + "'");
} catch (IOException e) {
e.printStackTrace();
redirectAttributes.addFlashAttribute("message",
"Failed to upload '" + file.getOriginalFilename() + "': " + e.getMessage());
} catch (IllegalStateException e) {
// 通常是因为 transferTo 被调用多次,或文件已移动/删除
e.printStackTrace();
redirectAttributes.addFlashAttribute("message",
"Could not upload the file: " + e.getMessage());
}
return "redirect:/uploadStatus"; // 创建一个显示上传状态的页面
}
@PostMapping("/uploadMultipleFiles")
public String uploadMultipleFiles(@RequestParam("files") MultipartFile[] files, // 或者 List files
@RequestParam("description") String description,
RedirectAttributes redirectAttributes) {
if (files.length == 0 || Arrays.stream(files).allMatch(MultipartFile::isEmpty)) {
redirectAttributes.addFlashAttribute("message", "Please select at least one file to upload.");
return "redirect:/uploadStatus";
}
StringBuilder uploadedFileNames = new StringBuilder();
for (MultipartFile file : files) {
if (file.isEmpty()) {
continue; // 跳过空文件
}
try {
File uploadDir = new File(UPLOAD_DIR);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
String originalFileName = file.getOriginalFilename();
String fileExtension = "";
if (originalFileName != null && originalFileName.lastIndexOf(".") != -1) {
fileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
}
String newFileName = UUID.randomUUID().toString() + fileExtension;
Path path = Paths.get(UPLOAD_DIR + newFileName);
file.transferTo(path.toFile());
uploadedFileNames.append(originalFileName).append(" (as ").append(newFileName).append("), ");
System.out.println("Uploaded file: " + originalFileName + " as " + newFileName);
} catch (IOException e) {
e.printStackTrace();
redirectAttributes.addFlashAttribute("message",
"Failed to upload one or more files: " + e.getMessage());
return "redirect:/uploadStatus";
}
}
System.out.println("Description: " + description);
redirectAttributes.addFlashAttribute("message",
"Successfully uploaded files: " + uploadedFileNames.toString().replaceAll(", $", ""));
return "redirect:/uploadStatus";
}
// 一个简单的页面来显示上传结果
@org.springframework.web.bind.annotation.GetMapping("/uploadStatus")
public String uploadStatus(Model model) {
// model 会自动包含 flash attributes
return "uploadStatus"; // uploadStatus.html 或 uploadStatus.jsp
}
}
创建一个 uploadStatus.html
(Thymeleaf 示例) 或 uploadStatus.jsp
来显示消息:
DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Upload Statustitle>
head>
<body>
<h2 th:if="${message}" th:text="${message}">Status Messageh2>
<a href="/your-app-context/">Back to Upload Forma>
body>
html>
MultipartFile
接口常用方法:
String getName()
: 获取表单中 name
属性的值。String getOriginalFilename()
: 获取上传文件的原始名称。注意:不要直接使用此名称保存文件,因为它可能包含路径字符或不安全字符。最好对其进行清理或生成新名称。String getContentType()
: 获取文件的 MIME 类型 (例如 image/jpeg
)。boolean isEmpty()
: 判断文件是否为空 (用户未选择文件)。long getSize()
: 获取文件大小 (字节)。byte[] getBytes()
: 获取文件的字节数组。注意:对于大文件,这可能会消耗大量内存。InputStream getInputStream()
: 获取文件的输入流。void transferTo(File dest)
: 将接收到的文件保存到目标文件。这是最常用的保存文件的方法。它通常会移动临时文件(如果文件大于 maxInMemorySize
)或复制内存中的内容。文件大小超限: 如果上传的文件超过了 maxUploadSize
或 maxUploadSizePerFile
(如果配置了),Spring 会抛出 MaxUploadSizeExceededException
(更具体的可能是 SizeLimitExceededException
或 FileSizeLimitExceededException
来自 Commons FileUpload)。你可以通过 @ControllerAdvice
来全局处理这个异常。
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public String handleMaxSizeException(MaxUploadSizeExceededException exc, RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("message", "File is too large! Maximum size allowed is " + exc.getMaxUploadSize() + " bytes.");
return "redirect:/uploadStatus"; // 或者你的上传页面
}
// 可以添加其他异常处理
}
IO 异常: 在 file.transferTo()
或处理输入流时可能发生 IOException
,需要 try-catch 处理。
commons-fileupload
和 commons-io
(如果使用 CommonsMultipartResolver
)。multipartResolver
的 MultipartResolver
bean (通常是 CommonsMultipartResolver
或 StandardServletMultipartResolver
),并设置相关属性 (如大小限制)。Spring Boot 用户可以在 application.properties
中配置。method="POST"
且 enctype="multipart/form-data"
的 HTML 表单,包含
。@RequestParam("yourFileName") MultipartFile file
(或 MultipartFile[] files
) 来接收上传的文件。使用 file.transferTo(destination)
保存文件。getOriginalFilename()
的值来构建服务器上的文件路径;要清理它或生成唯一的文件名。