核心概念
无论使用哪种方式,文件上传的原理基本相同:

- 前端: 在 HTML 表单中,必须设置
enctype="multipart/form-data",并且使用<input type="file">- 后端:
- Spring 框架(通过
CommonsMultipartResolver或StandardServletMultipartResolver)会检测到这是一个文件上传请求。 - 它会将请求体中的普通字段和文件内容解析出来。
- 会被封装在一个
MultipartFile对象中。 - 我们在 Controller 中接收这个
MultipartFile对象,然后就可以读取文件内容、获取文件名、文件大小等信息,并将其保存到服务器的指定路径。
- 后端:
Spring Boot (推荐)
Spring Boot极大地简化了文件上传的配置,开箱即用。
项目依赖
确保你的 pom.xml 中包含了 spring-boot-starter-web 依赖,它已经包含了所有必需的库。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
配置文件
在 application.properties 或 application.yml 中,你可以配置上传文件的一些参数,例如最大文件大小、最大请求大小等。
application.properties 示例:
# 单个文件最大大小 (10MB) spring.servlet.multipart.max-file-size=10MB # 整个请求最大大小 (包含多个文件和表单数据) (11MB) spring.servlet.multipart.max-request-size=11MB # 临时文件存放目录 (可选) # spring.servlet.multipart.location=/tmp
application.yml 示例:
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 11MB
创建 Controller
创建一个 Controller 来处理文件上传请求。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
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;
@Controller
public class FileUploadController {
// 定义一个固定的上传目录,在实际项目中最好从配置中读取
private static final String UPLOADED_FOLDER = "/path/to/your/upload/directory/";
@PostMapping("/upload")
public @ResponseBody String handleFileUpload(@RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes) {
if (file.isEmpty()) {
return "请选择一个文件上传";
}
try {
// 获取原始文件名
String originalFilename = file.getOriginalFilename();
// 构建文件保存路径
Path destination = Paths.get(UPLOADED_FOLDER + originalFilename);
// 将文件保存到指定路径
Files.copy(file.getInputStream(), destination);
return "文件上传成功: " + originalFilename;
} catch (IOException e) {
e.printStackTrace();
return "文件上传失败: " + e.getMessage();
}
}
}
代码解释:
@Controller: 声明这是一个 Spring MVC 的控制器。@PostMapping("/upload"): 映射 HTTP POST 请求到/upload路径。@RequestParam("file") MultipartFile file:@RequestParam("file")将 HTTP 请求中名为file的参数绑定到方法参数file上,这个file必须和 HTML 表单中<input name="file">的name属性一致。MultipartFile是 Spring 提供的接口,用于封装上传的文件信息。
file.isEmpty(): 检查文件是否为空。file.getOriginalFilename(): 获取客户端上传的原始文件名。file.getInputStream(): 获取文件的输入流。Files.copy(...): Java NIO 提供的高效文件复制方法,将输入流中的内容写入到目标路径。@ResponseBody: 将方法的返回值直接作为 HTTP 响应体返回。
创建 HTML 表单
在 src/main/resources/templates/ 目录下创建一个 upload.html 文件(如果你使用 Thymeleaf 等模板引擎)。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>文件上传</title>
</head>
<body>
<h1>文件上传示例</h1>
<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="file" />
<button type="submit">上传</button>
</form>
</body>
</html>
关键点:
method="POST": 文件上传必须使用 POST 方法。action="/upload": 指向后端处理上传的 Controller 路径。enctype="multipart/form-data": 这是必须的,它告诉浏览器要以二进制流的方式提交表单数据,才能正确上传文件。
传统 Spring MVC (非 Boot)
如果你使用的是传统的 Spring 项目,配置会稍微多一些。
项目依赖
需要添加 commons-fileupload 和 commons-io 依赖。
<dependencies>
<!-- Spring MVC 核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.23</version>
</dependency>
<!-- 文件上传依赖 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
配置 web.xml 和 DispatcherServlet
确保 web.xml 中配置了 DispatcherServlet。
<web-app ...>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
配置 Spring MVC 配置文件 (dispatcher-servlet.xml)
这是最关键的一步,需要配置 MultipartResolver。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 启用注解驱动 -->
<mvc:annotation-driven/>
<!-- 扫描 Controller -->
<context:component-scan base-package="com.yourpackage"/>
<!-- 配置 MultipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置上传文件的最大尺寸,单位是字节 (10MB) -->
<property name="maxUploadSize" value="10485760"/>
<!-- 设置请求的最大尺寸 (11MB) -->
<property name="maxInMemorySize" value="11534336"/>
<!-- 设置默认编码 -->
<property name="defaultEncoding" value="UTF-8"/>
</bean>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
关键点:
CommonsMultipartResolver: 这是 Spring MVC 中最常用的MultipartResolver实现。maxUploadSize: 设置单个请求的最大允许大小,防止用户上传超大文件耗尽服务器资源。defaultEncoding: 设置文件名和内容的默认编码,防止中文乱码。
Controller 和 HTML 表单
Controller 的代码和 Spring Boot 中的几乎完全一样,只是返回的视图名不同,HTML 表单也完全相同。
// Controller 代码 (与 Spring Boot 版本几乎一样)
@Controller
public class FileUploadController {
private static final String UPLOADED_FOLDER = "/path/to/your/upload/directory/";
@PostMapping("/upload")
public String handleFileUpload(@RequestParam("file") MultipartFile file,
Model model) {
if (file.isEmpty()) {
model.addAttribute("message", "请选择一个文件上传");
return "uploadStatus"; // 返回一个视图
}
try {
byte[] bytes = file.getBytes();
Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());
Files.write(path, bytes);
model.addAttribute("message", "文件上传成功: " + file.getOriginalFilename());
return "uploadStatus";
} catch (IOException e) {
e.printStackTrace();
model.addAttribute("message", "文件上传失败: " + e.getMessage());
return "uploadStatus";
}
}
}
进阶主题
文件名处理(防止覆盖和乱码)
直接使用 file.getOriginalFilename() 可能会有问题:
- 文件名冲突: 如果多个用户上传同名文件,后一个会覆盖前一个。
- 安全问题: 恶意用户可能上传带有 的文件名进行路径遍历攻击。
- 中文乱码: 不同浏览器编码方式不同,可能导致文件名乱码。
推荐做法:
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
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.nio.file.StandardCopyOption;
import java.util.UUID;
// ...
@PostMapping("/upload-safe")
public @ResponseBody String handleFileUploadSafe(@RequestParam("file") MultipartFile file) throws IOException {
if (file.isEmpty()) {
return "请选择一个文件上传";
}
// 1. 获取原始文件名
String originalFilename = file.getOriginalFilename();
// 2. 使用 UUID 生成新的文件名,防止覆盖
String extension = StringUtils.getFilenameExtension(originalFilename);
String newFileName = UUID.randomUUID().toString();
if (extension != null && !extension.isEmpty()) {
newFileName += "." + extension;
}
// 3. 构建安全的保存路径 (防止路径遍历)
// UPLOADED_FOLDER 应该是绝对路径,并且确保其安全性
Path destination = Paths.get(UPLOADED_FOLDER).resolve(newFileName).normalize();
// 确保目标路径在上传目录下
if (!destination.startsWith(Paths.get(UPLOADED_FOLDER).normalize())) {
throw new IOException("非法的文件路径");
}
// 4. 使用 NIO 的 copy 方法,它会自动处理文件创建和内容写入
Files.copy(file.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
return "文件上传成功,新文件名: " + newFileName;
}
文件存储到云服务(如 AWS S3, 阿里云 OSS)
在实际生产环境中,通常不会将文件直接存储在服务器的本地磁盘上,而是使用对象存储服务。
基本流程是:
- 在 Controller 中接收
MultipartFile。 - 使用云服务商的 SDK(如 AWS SDK for Java)。
- 从
MultipartFile获取InputStream和文件元数据(如Content-Type)。 - 调用云服务的 API(
s3Client.putObject())将输入流上传到云端。 - 返回文件在云端的 URL 给前端。
这样做的好处是高可用、可扩展、成本低,并且可以将应用服务器和存储服务器分离。
| 特性 | Spring Boot | 传统 Spring MVC |
|---|---|---|
| 依赖 | spring-boot-starter-web (自动包含) |
spring-webmvc, commons-fileupload |
| 配置 | application.properties / application.yml |
web.xml, dispatcher-servlet.xml (CommonsMultipartResolver) |
| 核心类 | MultipartFile |
MultipartFile |
| 优点 | 简单、快速、配置少,是现代开发的首选 | 灵活,但配置繁琐,适合老项目或复杂需求 |
对于新项目,强烈推荐使用 Spring Boot,它能让你更专注于业务逻辑,而不是繁琐的配置。
