当前位置: 首页 > web >正文

Spring MVC 如何处理文件上传? 需要哪些配置和依赖?如何在 Controller 中接收上传的文件 (MultipartFile)?

Spring MVC 处理文件上传主要依赖于 MultipartResolver 接口及其实现。最常用的实现是 CommonsMultipartResolver(基于 Apache Commons FileUpload)和 StandardServletMultipartResolver(基于 Servlet 3.0+ API)。

以下是如何配置和使用文件上传功能的步骤:

1. 依赖 (Dependencies)

需要添加相应的依赖到你pom.xml (Maven) 或 build.gradle (Gradle) 文件中。

a) 使用 Apache Commons FileUpload (推荐,功能更全面):
如果想使用 CommonsMultipartResolver,需要添加以下依赖:

<!-- pom.xml (Maven) -->
<dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.5</version> <!-- 使用最新稳定版 -->
</dependency>
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version> <!-- commons-fileupload 可能需要,或者你直接用它 -->
</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 中。

2. 配置 (Configuration)

你需要在 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+ 环境)/*@Beanpublic StandardServletMultipartResolver multipartResolver() {return new StandardServletMultipartResolver();}*/
}

注意:

  • Bean 的名称必须是 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.propertiesapplication.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 配置 (传统方式):

<!-- spring-mvc-config.xml -->
<bean id="multipartResolver"class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><!-- 设置上传文件的最大尺寸,单位为字节 --><property name="maxUploadSize" value="10485760"/> <!-- 10MB --><!-- 设置单个文件的最大尺寸,单位为字节 (Spring 5.0+ for CommonsMultipartResolver) --><property name="maxUploadSizePerFile" value="5242880"/> <!-- 5MB --><!-- 设置文件在内存中的最大大小,超过该大小会写入临时文件,单位为字节 --><property name="maxInMemorySize" value="1048576"/> <!-- 1MB --><!-- 设置默认编码 --><property name="defaultEncoding" value="UTF-8"/><!-- <property name="uploadTempDir" value="file:/tmp/uploads/"/> -->
</bean><!-- 或者,使用 StandardServletMultipartResolver -->
<!--
<bean id="multipartResolver"class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>
-->

3. HTML 表单

在你的 JSP 或 HTML 页面中,确保表单的 methodPOST,并且 enctype 设置为 multipart/form-data

<!DOCTYPE html>
<html>
<head><title>File Upload</title>
</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/

4. Controller 中接收文件

在 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<MultipartFile> 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 attributesreturn "uploadStatus"; // uploadStatus.html 或 uploadStatus.jsp}
}

创建一个 uploadStatus.html (Thymeleaf 示例) 或 uploadStatus.jsp 来显示消息:

<!-- uploadStatus.html (Thymeleaf) -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>Upload Status</title>
</head>
<body><h2 th:if="${message}" th:text="${message}">Status Message</h2><a href="/your-app-context/">Back to Upload Form</a> <!-- 调整链接 -->
</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)或复制内存中的内容。

5. 错误处理

  • 文件大小超限: 如果上传的文件超过了 maxUploadSizemaxUploadSizePerFile (如果配置了),Spring 会抛出 MaxUploadSizeExceededException (更具体的可能是 SizeLimitExceededExceptionFileSizeLimitExceededException 来自 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 处理。

总结

  1. 依赖: 添加 commons-fileuploadcommons-io (如果使用 CommonsMultipartResolver)。
  2. 配置: 在 Spring 配置中注册名为 multipartResolverMultipartResolver bean (通常是 CommonsMultipartResolverStandardServletMultipartResolver),并设置相关属性 (如大小限制)。Spring Boot 用户可以在 application.properties 中配置。
  3. HTML: 创建一个 method="POST"enctype="multipart/form-data" 的 HTML 表单,包含 <input type="file" name="yourFileName">
  4. Controller: 在 Controller 方法中使用 @RequestParam("yourFileName") MultipartFile file (或 MultipartFile[] files) 来接收上传的文件。使用 file.transferTo(destination) 保存文件。
  5. 安全:
    • 不要信任 getOriginalFilename() 的值来构建服务器上的文件路径;要清理它或生成唯一的文件名。
    • 验证文件类型 (MIME 和扩展名)。
    • 考虑将上传的文件存储在 Web 应用根目录之外的安全位置。
  6. 错误处理: 处理文件大小超限异常和 IO 异常。
http://www.xdnf.cn/news/6934.html

相关文章:

  • Selenium无法定位元素的几种解决方案详解
  • AgentCPM-GUI,清华联合面壁智能开源的端侧GUI智能体模型
  • 远程主机状态监控-GPU服务器状态监控-深度学习服务器状态监控
  • 使用ts-node搭建typescript运行环境
  • Java Stream流:高效数据处理的现代解决方案
  • 操作系统学习笔记第4章 (竟成)
  • JavaScript性能优化实战(11):前沿技术在性能优化中的应用
  • 基于Spring Boot和Vue的在线考试系统架构设计与实现(源码+论文+部署讲解等)
  • Canva 推出自有应用生成器以与 Bolt 和 Lovable 竞争
  • 2025年渗透测试面试题总结-安恒[实习]安全工程师(题目+回答)
  • 谈谈未来iOS越狱或巨魔是否会消失
  • 卸载和安装JDK
  • 【持续更新中】架构面试知识学习总结
  • 布隆过滤器深度解析
  • 【OpenGL学习】(二)OpenGL渲染简单图形
  • Spring6学习及复习笔记
  • flutter 配置 安卓、Ios启动图
  • CoverM:contig/bin的相对丰度计算
  • 数字万用表与指针万用表使用方法及注意事项
  • Redis键(Key)操作完全指南:从基础到高级应用
  • Java-Objects类高效应用的全面指南
  • Animaster:一次由 CodeBuddy 主导的 CSS 动画编辑器诞生记
  • 小型气象站应用之鱼塘养殖方案
  • GitHub文档加载器设计与实现
  • Win11下轻松搭建wiki.js,Docker.desktop部署指南(mysql+elasticsearch+kibana+wiki.js)
  • 国内AWS CloudFront与S3私有桶集成指南:安全访问静态内容
  • 用Python玩转人工智能——数字识别技术 之三
  • React 中,闭包陷阱
  • hadoop.proxyuser.代理用户.授信域 用来干什么的
  • 【目标检测】【Transformer】Swin Transformer