在 JavaWeb 开发中,文件上传是核心功能之一,不同的技术栈和开发场景对应不同的实现方式。本文将全面讲解JavaWeb 中所有主流的文件上传方式 ,包括传统 Commons FileUpload 组件 、Servlet 3.0 原生上传 、Spring MVC 上传 、Spring Boot 上传(单文件 / 多文件、本地 / 云存储),并提供逐行注释的完整代码,确保零基础也能掌握每种方式的实现逻辑。
一、文件上传的基础前提
无论使用哪种实现方式,文件上传的HTTP 协议基础要求是统一的:
- 表单提交方式必须为
POST; - 表单的
enctype属性必须设置为multipart/form-data(默认application/x-www-form-urlencoded无法传输二进制文件); - 表单中通过
<input type="file" name="xxx">标签选择文件,多文件上传添加multiple属性。
前端通用上传表单(upload.html):
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>文件上传通用表单</title>
</head>
<body>
<h3>单文件上传</h3>
<form action="对应后端接口路径" method="post" enctype="multipart/form-data">
用户名:<input type="text" name="username"><br><br>
选择文件:<input type="file" name="file"><br><br>
<input type="submit" value="上传">
</form>
<h3>多文件上传</h3>
<form action="对应后端接口路径" method="post" enctype="multipart/form-data">
选择多个文件:<input type="file" name="files" multiple><br><br>
<input type="submit" value="批量上传">
</form>
</body>
</html>
二、方式一:传统 Commons FileUpload 组件(Servlet 2.5 及以下)
这是Servlet 3.0 之前的主流实现方式 ,依赖 Apache 的commons-fileupload和commons-io两个 jar 包,适用于老旧项目。
2.1 环境准备
Maven 依赖
XML
<!-- 文件上传核心依赖 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- 依赖的IO工具包 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
</dependency>
<!-- ServletAPI依赖(Tomcat已提供,设为provided) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
非 Maven 项目
手动下载 jar 包放入WEB-INF/lib:
2.2 后端实现(单 / 多文件上传)
java
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
/**
* Commons FileUpload实现文件上传(支持单/多文件)
*/
@WebServlet("/commonsUpload")
public class CommonsFileUploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置编码,解决中文乱码
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 1. 判断请求是否为multipart/form-data类型
if (!ServletFileUpload.isMultipartContent(request)) {
response.getWriter().write("请求不是文件上传类型,请检查表单enctype!");
return;
}
try {
// 2. 创建文件上传工厂,设置临时目录和内存阈值
DiskFileItemFactory factory = new DiskFileItemFactory();
String tempPath = this.getServletContext().getRealPath("/temp"); // 临时目录
File tempDir = new File(tempPath);
if (!tempDir.exists()) tempDir.mkdirs(); // 不存在则创建
factory.setRepository(tempDir); // 设置临时文件存储目录
factory.setSizeThreshold(1024 * 1024); // 1MB内存阈值,超过则写入临时文件
// 3. 创建文件上传核心处理类
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setFileSizeMax(5 * 1024 * 1024); // 单个文件最大5MB
upload.setSizeMax(20 * 1024 * 1024); // 总文件最大20MB
upload.setHeaderEncoding("UTF-8"); // 文件名编码
// 4. 解析请求,将表单项封装为FileItem列表
List<FileItem> fileItems = upload.parseRequest(request);
// 5. 遍历处理每个表单项
for (FileItem item : fileItems) {
if (item.isFormField()) {
// 处理普通表单字段(如用户名)
String fieldName = item.getFieldName();
String fieldValue = item.getString("UTF-8");
System.out.println("普通字段:" + fieldName + " = " + fieldValue);
} else {
// 处理文件字段(单/多文件统一处理)
handleFile(item, request);
}
}
response.getWriter().write("文件上传成功!");
} catch (FileUploadException e) {
e.printStackTrace();
response.getWriter().write("上传失败:" + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
response.getWriter().write("服务器异常:" + e.getMessage());
}
}
/**
* 处理文件上传的核心方法
* @param item 文件项
* @param request 请求对象
* @throws Exception 异常
*/
private void handleFile(FileItem item, HttpServletRequest request) throws Exception {
// 获取原始文件名(处理IE浏览器的完整路径问题)
String originalFileName = item.getName();
if (originalFileName != null && originalFileName.contains("\\")) {
originalFileName = originalFileName.substring(originalFileName.lastIndexOf("\\") + 1);
}
// 未选择文件则直接返回
if (originalFileName == null || originalFileName.trim().isEmpty()) return;
// 生成唯一文件名,防止覆盖
String uniqueFileName = UUID.randomUUID().toString() + "_" + originalFileName;
// 获取上传目标目录
String uploadPath = this.getServletContext().getRealPath("/upload");
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) uploadDir.mkdirs();
// 保存文件到服务器
File saveFile = new File(uploadDir, uniqueFileName);
item.write(saveFile);
// 删除临时文件(超大文件会生成临时文件)
item.delete();
// 输出文件信息
System.out.println("原始文件名:" + originalFileName);
System.out.println("保存文件名:" + uniqueFileName);
System.out.println("保存路径:" + saveFile.getAbsolutePath());
}
}
三、方式二:Servlet 3.0 原生上传(无第三方依赖)
Servlet 3.0 及以上版本提供了原生的文件上传 API ,无需引入第三方 jar 包,通过Part接口实现文件上传,是轻量级项目的首选。
3.1 核心 API 说明
request.getPart(String name):获取单个文件的 Part 对象;request.getParts():获取所有 Part 对象(多文件上传);Part.write(String fileName):将文件写入服务器;Part.getSubmittedFileName():获取上传文件的原始名称。
3.2 后端实现(单 / 多文件上传)
java
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* Servlet 3.0原生文件上传
* @MultipartConfig:必须添加该注解,开启文件上传支持
* 配置参数说明:
* - fileSizeThreshold:内存阈值(1MB)
* - maxFileSize:单个文件最大大小(5MB)
* - maxRequestSize:总请求大小(20MB)
* - location:临时文件存储目录
*/
@WebServlet("/nativeUpload")
@MultipartConfig(
fileSizeThreshold = 1024 * 1024,
maxFileSize = 5 * 1024 * 1024,
maxRequestSize = 20 * 1024 * 1024,
location = "/temp"
)
public class NativeFileUploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置编码
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 1. 处理普通表单字段(如用户名)
String username = request.getParameter("username");
System.out.println("普通字段:username = " + username);
try {
// 2. 单文件上传处理(name为file的文件)
Part singleFilePart = request.getPart("file");
if (singleFilePart != null && singleFilePart.getSize() > 0) {
handlePart(singleFilePart, request);
}
// 3. 多文件上传处理(name为files的文件)
for (Part part : request.getParts()) {
if ("files".equals(part.getName()) && part.getSize() > 0) {
handlePart(part, request);
}
}
response.getWriter().write("Servlet 3.0原生上传成功!");
} catch (Exception e) {
e.printStackTrace();
response.getWriter().write("上传失败:" + e.getMessage());
}
}
/**
* 处理Part对象,保存文件到服务器
* @param part 文件Part对象
* @param request 请求对象
* @throws IOException 异常
*/
private void handlePart(Part part, HttpServletRequest request) throws IOException {
// 获取原始文件名
String originalFileName = part.getSubmittedFileName();
if (originalFileName == null || originalFileName.trim().isEmpty()) return;
// 生成唯一文件名
String uniqueFileName = UUID.randomUUID().toString() + "_" + originalFileName;
// 获取上传目录
String uploadPath = this.getServletContext().getRealPath("/upload");
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) uploadDir.mkdirs();
// 保存文件(Part的write方法自动处理IO,无需手动流操作)
part.write(uploadPath + File.separator + uniqueFileName);
// 输出文件信息
System.out.println("原始文件名:" + originalFileName);
System.out.println("保存文件名:" + uniqueFileName);
System.out.println("文件大小:" + part.getSize() + "字节");
}
}
关键注解 :@MultipartConfig是 Servlet 3.0 原生上传的核心,必须添加,否则无法获取Part对象。
四、方式三:Spring MVC 文件上传(基于 Servlet 3.0)
Spring MVC 对 Servlet 3.0 的文件上传进行了封装和简化 ,通过MultipartFile接口实现文件上传,是企业级 JavaWeb 项目的主流方式。
4.1 环境准备
Maven 依赖
XML
<!-- Spring MVC核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.30</version>
</dependency>
<!-- ServletAPI依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- 文件上传依赖(Spring MVC封装用) -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
Spring MVC 配置(xml 方式)
在spring-mvc.xml中配置文件上传解析器:
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 组件扫描,扫描Controller -->
<context:component-scan base-package="com.controller"/>
<!-- 开启MVC注解驱动 -->
<mvc:annotation-driven/>
<!-- 配置文件上传解析器,id必须为multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/> <!-- 编码 -->
<property name="maxUploadSizePerFile" value="5242880"/> <!-- 单个文件5MB -->
<property name="maxUploadSize" value="20971520"/> <!-- 总文件20MB -->
<property name="uploadTempDir" value="WEB-INF/temp"/> <!-- 临时目录 -->
</bean>
</beans>
4.2 后端 Controller 实现
java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* Spring MVC文件上传Controller
*/
@Controller
public class SpringMvcUploadController {
/**
* 单文件上传
* @param username 普通表单字段
* @param file 上传的文件(name必须与前端input的name一致)
* @param request 请求对象
* @return 上传结果
*/
@PostMapping("/springMvcSingleUpload")
@ResponseBody
public String singleUpload(String username, MultipartFile file, HttpServletRequest request) {
// 处理普通字段
System.out.println("普通字段:username = " + username);
// 判断是否选择文件
if (file.isEmpty()) {
return "请选择要上传的文件!";
}
try {
handleMultipartFile(file, request);
return "Spring MVC单文件上传成功!";
} catch (IOException e) {
e.printStackTrace();
return "上传失败:" + e.getMessage();
}
}
/**
* 多文件上传
* @param files 多文件数组(name必须与前端input的name一致)
* @param request 请求对象
* @return 上传结果
*/
@PostMapping("/springMvcMultiUpload")
@ResponseBody
public String multiUpload(MultipartFile[] files, HttpServletRequest request) {
// 判断是否选择文件
if (files == null || files.length == 0) {
return "请选择要上传的文件!";
}
// 遍历处理每个文件
for (MultipartFile file : files) {
if (!file.isEmpty()) {
try {
handleMultipartFile(file, request);
} catch (IOException e) {
e.printStackTrace();
return "批量上传失败:" + e.getMessage();
}
}
}
return "Spring MVC多文件上传成功!";
}
/**
* 处理MultipartFile,保存文件到服务器
* @param file 上传的文件
* @param request 请求对象
* @throws IOException 异常
*/
private void handleMultipartFile(MultipartFile file, HttpServletRequest request) throws IOException {
// 获取原始文件名
String originalFileName = file.getOriginalFilename();
// 生成唯一文件名
String uniqueFileName = UUID.randomUUID().toString() + "_" + originalFileName;
// 获取上传目录
ServletContext servletContext = request.getServletContext();
String uploadPath = servletContext.getRealPath("/upload");
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) uploadDir.mkdirs();
// 保存文件(transferTo方法自动处理IO)
File saveFile = new File(uploadDir, uniqueFileName);
file.transferTo(saveFile);
// 输出文件信息
System.out.println("原始文件名:" + originalFileName);
System.out.println("保存文件名:" + uniqueFileName);
System.out.println("文件大小:" + file.getSize() + "字节");
}
}
五、方式四:Spring Boot 文件上传(最简实现)
Spring Boot 对 Spring MVC 的文件上传进行了自动配置,无需手动配置文件上传解析器,是目前最主流的实现方式,支持本地存储和云存储(如阿里云 OSS)。
5.1 环境准备
Maven 依赖(Spring Boot 2.7.x)
XML
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/>
</parent>
<dependencies>
<!-- Spring Boot Web依赖(自动包含文件上传相关封装) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
配置文件(application.yml)
XML
spring:
# 文件上传配置
servlet:
multipart:
enabled: true # 开启文件上传
default-encoding: UTF-8 # 编码
max-file-size: 5MB # 单个文件最大大小
max-request-size: 20MB # 总请求最大大小
location: ./temp # 临时文件目录
# 自定义上传目录(非必须,也可在代码中硬编码)
upload:
path: ./upload # 本地存储目录(项目根目录下的upload文件夹)
5.2 本地存储实现(单 / 多文件)
java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* Spring Boot文件上传(本地存储)
*/
@RestController
public class SpringBootUploadController {
// 从配置文件中读取上传目录
@Value("${upload.path}")
private String uploadPath;
/**
* 单文件上传
* @param username 普通表单字段
* @param file 上传的文件
* @return 上传结果
*/
@PostMapping("/bootSingleUpload")
public String singleUpload(String username, MultipartFile file) {
System.out.println("普通字段:username = " + username);
if (file.isEmpty()) {
return "请选择文件!";
}
try {
saveFile(file);
return "Spring Boot单文件上传成功!";
} catch (IOException e) {
e.printStackTrace();
return "上传失败:" + e.getMessage();
}
}
/**
* 多文件上传
* @param files 多文件数组
* @return 上传结果
*/
@PostMapping("/bootMultiUpload")
public String multiUpload(MultipartFile[] files) {
if (files == null || files.length == 0) {
return "请选择文件!";
}
for (MultipartFile file : files) {
if (!file.isEmpty()) {
try {
saveFile(file);
} catch (IOException e) {
e.printStackTrace();
return "批量上传失败:" + e.getMessage();
}
}
}
return "Spring Boot多文件上传成功!";
}
/**
* 保存文件到本地
* @param file 上传的文件
* @throws IOException 异常
*/
private void saveFile(MultipartFile file) throws IOException {
// 获取原始文件名
String originalFileName = file.getOriginalFilename();
// 生成唯一文件名
String uniqueFileName = UUID.randomUUID().toString() + "_" + originalFileName;
// 创建上传目录
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) uploadDir.mkdirs();
// 保存文件
File saveFile = new File(uploadDir, uniqueFileName);
file.transferTo(saveFile);
// 输出文件信息
System.out.println("原始文件名:" + originalFileName);
System.out.println("保存路径:" + saveFile.getAbsolutePath());
}
}
5.3 云存储实现(阿里云 OSS 示例)
实际项目中,文件通常不会存储在服务器本地(易丢失、占用服务器磁盘),而是上传到云存储服务(如阿里云 OSS、腾讯云 COS)。以下是 Spring Boot 集成阿里云 OSS 的文件上传实现:
5.3.1 阿里云 OSS 依赖(更建议依据官方SDK文档进行)
XML
<!-- 阿里云OSS SDK -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.0</version>
</dependency>
5.3.2 阿里云 OSS 配置(application.yml)
XML
aliyun:
oss:
endpoint: oss-cn-beijing.aliyuncs.com # OSS地域节点
access-key-id: 你的AccessKeyId # 阿里云AccessKey
access-key-secret: 你的AccessKeySecret # 阿里云AccessKeySecret
bucket-name: 你的Bucket名称 # OSS存储空间名称
5.3.3 OSS 工具类
java
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.UUID;
/**
* 阿里云OSS文件上传工具类
*/
@Component
public class AliOssUtil {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.access-key-id}")
private String accessKeyId;
@Value("${aliyun.oss.access-key-secret}")
private String accessKeySecret;
@Value("${aliyun.oss.bucket-name}")
private String bucketName;
/**
* 上传文件到阿里云OSS
* @param file 上传的文件
* @return 文件的访问URL
* @throws Exception 异常
*/
public String upload(MultipartFile file) throws Exception {
// 获取原始文件名
String originalFileName = file.getOriginalFilename();
// 生成唯一文件名
String objectName = UUID.randomUUID().toString() + "_" + originalFileName;
// 创建OSS客户端
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 获取文件输入流
InputStream inputStream = file.getInputStream();
// 上传文件到OSS
ossClient.putObject(bucketName, objectName, inputStream);
// 生成文件访问URL
return "https://" + bucketName + "." + endpoint + "/" + objectName;
} finally {
// 关闭OSS客户端
ossClient.shutdown();
}
}
}
5.3.4 OSS 上传 Controller
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
/**
* Spring Boot集成阿里云OSS文件上传
*/
@RestController
public class AliOssUploadController {
@Resource
private AliOssUtil aliOssUtil;
@PostMapping("/ossUpload")
public String ossUpload(MultipartFile file) {
if (file.isEmpty()) {
return "请选择文件!";
}
try {
String fileUrl = aliOssUtil.upload(file);
return "阿里云OSS上传成功,文件URL:" + fileUrl;
} catch (Exception e) {
e.printStackTrace();
return "OSS上传失败:" + e.getMessage();
}
}
}
六、各上传方式对比与适用场景
| 实现方式 | 依赖 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Commons FileUpload | 第三方 jar 包 | 兼容老旧 Servlet 版本 | 代码繁琐、依赖第三方 | Servlet 2.5 及以下老旧项目 |
| Servlet 3.0 原生 | 无 | 轻量级、无依赖 | 代码冗余、需手动处理细节 | 轻量级 Servlet 项目(3.0+) |
| Spring MVC | Spring MVC+Commons FileUpload | 封装性好、企业级标准 | 需手动配置解析器 | 传统 Spring MVC 项目 |
| Spring Boot 本地存储 | Spring Boot Web | 自动配置、代码极简 | 仅本地存储,易丢失 | 小型项目、测试环境 |
| Spring Boot 云存储 | Spring Boot + 云存储 SDK | 高可用、易扩展、不占服务器磁盘 | 需购买云服务 | 生产环境、企业级项目 |
七、常见问题与通用解决方案
- 中文乱码:设置请求 / 响应编码为 UTF-8,文件上传组件的默认编码也设为 UTF-8;
- 文件覆盖:使用 UUID、时间戳生成唯一文件名;
- 文件大小限制:在组件 / 配置中设置最大文件大小,捕获超限异常;
- 文件类型限制 :判断文件后缀名或 MIME 类型(如
file.getContentType().startsWith("image/")); - 临时文件残留:上传完成后手动删除临时文件(Commons FileUpload)或依赖框架自动清理。
八、总结
本文覆盖了 JavaWeb 中所有主流的文件上传方式,从传统的 Commons FileUpload 组件到现代的 Spring Boot 云存储,每种方式都提供了完整的代码实现和注释。在实际开发中,应根据项目的技术栈(Servlet 版本、是否使用 Spring)和部署环境(测试 / 生产)选择合适的实现方式:
- 老旧项目选Commons FileUpload;
- 轻量级 Servlet 项目选Servlet 3.0 原生;
- 企业级项目优先选Spring Boot + 云存储 (生产环境)或Spring MVC(传统框架)。
掌握这些方式后,可轻松应对用户头像上传、附件管理、图片服务器等实际业务场景的文件上传需求!