文件上传在 Web 开发中非常常见,例如用户上传头像、附件等,在 Servlet 3.0 之前,处理文件上传非常繁琐,需要自己解析复杂的 multipart/form-data 格式,但从 Servlet 3.0 开始,官方提供了强大的 Part API,让文件上传变得异常简单。

下面我将分步讲解,从核心概念到完整代码示例。
核心概念:multipart/form-data
当你的 HTML 表单中包含文件输入框 (<input type="file">) 时,必须将表单的 enctype 属性设置为 multipart/form-data。
application/x-www-form-urlencoded: 默认的编码方式,它只能处理文本数据,无法处理文件。multipart/form-data: 这是一种特殊的编码类型,它可以将表单中的所有部分(包括文本字段和文件)分割成多个“部分”(Part)进行上传,每个部分都有自己的边界(Boundary),服务器可以根据这个边界来解析出不同的数据。
Servlet 3.0+ 的 Part API
Servlet 3.0 引入了 javax.servlet.http.Part 接口,它代表上传文件中的一个“部分”,通过这个 API,你可以轻松地获取文件信息、读取文件内容以及保存文件。
Part 接口的主要方法:

String getContentType(): 获取上传文件的 Content-Type(image/jpeg)。String getName(): 获取表单中对应字段的name属性值。String getSubmittedFileName(): 获取客户端原始的文件名,这是最常用的方法之一。long getSize(): 获取上传文件的大小(字节)。InputStream getInputStream(): 获取一个输入流,用于读取文件的内容。void write(String path): 将上传的文件内容直接写入到服务器指定的路径,这是最简单、最高效的保存文件的方法。void delete(): 当临时文件不再需要时,调用此方法将其删除。
实现步骤
我们将创建一个完整的文件上传示例,包含前端 JSP 页面和后端 Servlet。
第 1步:配置 web.xml (可选)
虽然现代 Web 应用(特别是使用注解的)通常不需要 web.xml,但如果你使用的是较老的项目或者想配置 Servlet 的 <multipart-config>,web.xml 是一个标准的地方。
<!-- web.xml -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>FileUploadServlet</servlet-name>
<servlet-class>com.example.FileUploadServlet</servlet-class>
<!-- 配置文件上传的相关参数 -->
<multipart-config>
<!-- 临时文件存放的目录,默认是系统的临时目录 -->
<location>/tmp</location>
<!-- 上传文件总大小的最大值 (e.g., 5MB) -->
<max-file-size>5242880</max-file-size>
<!-- 请求中所有文件总大小的最大值 (e.g., 20MB) -->
<max-request-size>20971520</max-request-size>
<!-- 当文件大小超过 max-file-size 时,写入临时文件前的缓冲大小 (e.g., 1MB) -->
<file-size-threshold>1048576</file-size-threshold>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>FileUploadServlet</servlet-name>
<url-pattern>/upload</url-pattern>
</servlet-mapping>
</web-app>
注意:<multipart-config> 也可以直接在 Servlet 类上使用 @MultipartConfig 注解来配置,这更现代。
第 2步:创建前端上传页面 (upload.jsp)
这个页面包含一个文件输入框和一个提交按钮。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>文件上传</title>
</head>
<body>
<h1>选择文件上传</h1>
<form action="upload" method="post" enctype="multipart/form-data">
<label for="file">选择文件:</label>
<input type="file" id="file" name="file" required>
<br><br>
<input type="submit" value="上传">
</form>
</body>
</html>
关键点:
action="upload": 指向 Servlet 的 URL 映射。method="post": 文件上传必须使用POST方法。enctype="multipart/form-data": 必须设置,否则文件无法上传。
第 3步:创建后端 Servlet (FileUploadServlet.java)
这是处理文件上传的核心逻辑。
package com.example;
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.io.PrintWriter;
import java.nio.file.Paths;
// 使用注解来配置 multipart,它会覆盖 web.xml 中的配置(如果存在)
@WebServlet("/upload")
@MultipartConfig(
fileSizeThreshold = 1024 * 1024 * 2, // 2 MB
maxFileSize = 1024 * 1024 * 10, // 10 MB
maxRequestSize = 1024 * 1024 * 50 // 50 MB
)
public class FileUploadServlet extends HttpServlet {
// 定义在服务器上保存上传文件的目录
private static final String UPLOAD_DIR = "uploads";
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 设置响应内容类型
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 2. 获取上传文件的目录
// getServletContext().getRealPath() 获取 Web 应用在服务器上的真实路径
String applicationPath = getServletContext().getRealPath("");
String uploadFilePath = applicationPath + File.separator + UPLOAD_DIR;
// 如果上传目录不存在,则创建
File uploadFileDir = new File(uploadFilePath);
if (!uploadFileDir.exists()) {
uploadFileDir.mkdirs();
}
// 3. 处理上传的文件
// request.getParts() 获取所有上传的 Part
for (Part part : request.getParts()) {
// 过滤掉不是文件上传的普通表单字段(例如文本输入)
// Part 是一个文件,它会有一个 Content-Disposition 头
if (part.getContentType() != null) {
String fileName = Paths.get(part.getSubmittedFileName()).getFileName().toString();
out.println("正在上传文件: " + fileName + "<br>");
// 4. 将文件写入服务器
// part.write() 是最简单的方法,它会将文件内容写入到指定的路径
part.write(uploadFilePath + File.separator + fileName);
out.println("文件已成功保存到: " + uploadFilePath + File.separator + fileName + "<br>");
}
}
out.println("<br><a href='upload.jsp'>返回上传页面</a>");
}
}
代码解析:
@WebServlet("/upload"): 将 Servlet 映射到/uploadURL。@MultipartConfig: 这个注解是关键,它告诉容器这个 Servlet 需要处理multipart请求,里面的参数配置了文件上传的大小限制等,与web.xml中的<multipart-config>作用相同。UPLOAD_DIR: 定义了一个相对路径uploads,用于存放上传的文件,我们使用getServletContext().getRealPath("")来获取 Web 应用的根目录在服务器上的绝对路径,然后拼接上uploads,得到最终的存储路径。request.getParts(): 获取请求中所有的Part对象,一个Part可以是一个文件,也可以是一个普通的表单字段。part.getContentType() != null: 这是一个简单的判断,用来区分文件 Part 和普通文本 Part,文件 Part 有Content-Type,而普通文本 Part 没有。part.getSubmittedFileName(): 获取用户在客户端选择的原始文件名。part.write(...): 这是保存文件的核心方法,它将上传的文件内容直接写入到指定的服务器路径,这个方法会处理文件流的读写,非常方便。
运行和测试
- 部署项目: 将你的 Web 应用(包含
upload.jsp,FileUploadServlet.class等)部署到 Tomcat 服务器上。 - 启动服务器: 启动 Tomcat。
- 访问页面: 在浏览器中访问
http://localhost:8080/你的项目名/upload.jsp。 - 选择并上传: 选择一个文件,点击“上传”按钮。
- 检查结果:
- 页面会显示上传成功的消息。
- 在你的 Tomcat 部署目录下(
apache-tomcat-9.0.x/webapps/你的项目名/),你会发现多了一个uploads文件夹,里面就是你上传的文件。
高级处理与注意事项
防止文件名冲突和恶意文件
上面的示例很简单,但在生产环境中,直接使用用户提供的文件名是不安全的。
- 文件名冲突: 如果多个用户上传同名文件,后一个会覆盖前一个。
- 安全风险: 恶意用户可能上传
.jsp,.exe,.php等脚本文件,如果服务器配置不当,可能会导致代码执行风险。
改进建议:
-
生成唯一文件名: 使用 UUID (Universally Unique Identifier) 来生成一个唯一的文件名。
import java.util.UUID; // ... String originalFileName = part.getSubmittedFileName(); String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".")); String uniqueFileName = UUID.randomUUID().toString() + fileExtension; // ... part.write(uploadFilePath + File.separator + uniqueFileName); -
限制文件类型: 检查文件的
Content-Type或者文件扩展名,只允许上传特定类型的文件。String contentType = part.getContentType(); if (contentType != null && (contentType.startsWith("image/jpeg") || contentType.startsWith("image/png"))) { // 允许上传 } else { // 拒绝上传,并返回错误信息 out.println("错误:只允许上传 JPG 或 PNG 图片!"); return; } -
限制文件扩展名: 更严格的做法是检查文件扩展名。
String allowedFileExtensions = ".jpg,.jpeg,.png,.gif"; String originalFileName = part.getSubmittedFileName(); String fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".")).toLowerCase(); if (!allowedFileExtensions.contains(fileExtension)) { out.println("错误:不允许的文件类型!"); return; }
手动处理文件流(不使用 part.write())
虽然 part.write() 很方便,但有时你可能需要更精细的控制,比如将文件保存到数据库或进行内存处理,这时可以使用 getInputStream()。
// 在 for 循环内
if (part.getContentType() != null) {
try (InputStream fileContent = part.getInputStream();
FileOutputStream outputStream = new FileOutputStream(new File(uploadFilePath, uniqueFileName))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fileContent.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
out.println("文件已成功保存到: " + uploadFilePath + File.separator + uniqueFileName + "<br>");
} catch (IOException e) {
e.printStackTrace();
out.println("文件保存失败: " + e.getMessage());
}
}
处理 Java Servlet 文件上传的最佳实践是:
- 使用 Servlet 3.0+ 的
@MultipartConfig注解,配置好文件大小限制。 - 在前端 JSP 中,表单必须设置
method="post"和enctype="multipart/form-data"。 - 在后端 Servlet 中,通过
request.getParts()获取所有上传部分。 - 通过
getContentType()区分文件和普通字段。 - 使用
getSubmittedFileName()获取原始文件名,并强烈建议使用 UUID 生成唯一文件名以避免冲突和安全问题。 - 使用
part.write()或手动流操作 将文件保存到服务器的安全目录中。
