Servlet 3.0+ 和 Apache Commons FileUpload,Servlet 3.0+ 提供了更简单、更原生的文件上传 API,而 Commons FileUpload 则是一个功能强大且稳定的库,能更好地处理各种边界情况(如大文件、内存限制等)。

整体思路
- 前端 (JSP):创建一个包含
<input type="file">的表单,用于用户选择图片,表单的enctype必须设置为multipart/form-data。 - 后端 (Servlet):创建一个 Servlet 来接收前端的请求。
- 处理上传:
- 使用
commons-fileupload解析multipart请求,从中提取出上传的文件。 - 验证文件类型(确保是图片)和文件大小。
- 为文件生成一个唯一的文件名,防止文件名冲突。
- 将文件保存到服务器的指定目录中。
- 使用
- 响应:根据处理结果,向前端返回成功或失败的信息。
第一步:项目环境准备
-
创建 Web 项目:在你的 IDE(如 Eclipse, IntelliJ IDEA)中创建一个 Dynamic Web Project。
-
添加依赖库:你需要将以下 JAR 包添加到项目的
WEB-INF/lib目录中。commons-fileupload-x.x.jar(commons-fileupload-1.4.jar)commons-io-x.x.jar(commons-io-2.11.jar),FileUpload 依赖它来简化 IO 操作。
你可以从 Maven 中央仓库下载它们:
第二步:前端页面 (index.jsp)
创建一个 JSP 页面,让用户可以选择图片并提交。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">图片上传示例</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.container { max-width: 600px; margin: auto; padding: 20px; border: 1px solid #ccc; border-radius: 5px; }
h2 { text-align: center; }
input[type="file"] { width: 100%; padding: 10px; margin-bottom: 10px; }
input[type="submit"] { background-color: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
input[type="submit"]:hover { background-color: #45a049; }
.message { margin-top: 20px; padding: 10px; border-radius: 4px; }
.success { background-color: #dff0d8; color: #3c763d; }
.error { background-color: #f2dede; color: #a94442; }
</style>
</head>
<body>
<div class="container">
<h2>上传图片</h2>
<form action="upload" method="post" enctype="multipart/form-data">
<label for="imageFile">选择图片文件:</label>
<input type="file" id="imageFile" name="imageFile" accept="image/*" required>
<input type="submit" value="上传">
</form>
<%-- 用于显示上传结果 --%>
<div id="message-container"></div>
</div>
<script>
// 使用 JavaScript 来显示服务器返回的消息
window.onload = function() {
const urlParams = new URLSearchParams(window.location.search);
const message = urlParams.get('message');
const messageType = urlParams.get('type');
if (message) {
const messageContainer = document.getElementById('message-container');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${messageType}`;
messageDiv.textContent = message;
messageContainer.appendChild(messageDiv);
}
};
</script>
</body>
</html>
关键点说明:
<form>标签的enctype="multipart/form-data"是必须的,它告诉浏览器要以二进制流的方式发送表单数据,而不是以application/x-www-form-urlencoded的方式。<input type="file">创建了文件选择控件。name="imageFile":这个name属性非常重要,后端 Servlet 将通过这个name来获取上传的文件。accept="image/*":这是一个前端提示,告诉浏览器只允许选择图片文件,但不能完全依赖它进行安全验证,后端必须再次验证。
第三步:后端 Servlet (UploadServlet.java)
这是处理上传的核心逻辑。
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
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 org.apache.commons.io.FilenameUtils;
@WebServlet("/upload")
@MultipartConfig // 必须注解,以支持 Servlet 3.0 的文件上传功能
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
// 定义上传文件存储的目录
private static final String UPLOAD_DIR = "uploads";
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 检查上传目录是否存在,如果不存在则创建
// getServletContext().getRealPath() 获取 Web 应用在服务器上的真实路径
String applicationPath = getServletContext().getRealPath("");
String uploadPath = applicationPath + File.separator + UPLOAD_DIR;
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
uploadDir.mkdir();
}
try {
// 2. 获取上传的 Part 对象 (Servlet 3.0+ API)
Part filePart = request.getPart("imageFile");
if (filePart == null) {
// 如果没有文件被选择
response.sendRedirect("index.jsp?message=请选择一个文件&type=error");
return;
}
// 3. 获取文件名并进行处理
String fileName = getSubmittedFileName(filePart);
if (fileName == null || fileName.isEmpty()) {
response.sendRedirect("index.jsp?message=文件名无效&type=error");
return;
}
// 4. 验证文件类型 (只允许图片)
String fileExtension = FilenameUtils.getExtension(fileName);
if (!isImage(fileExtension)) {
response.sendRedirect("index.jsp?message=只允许上传图片文件 (jpg, png, gif等)&type=error");
return;
}
// 5. 验证文件大小 (限制为 5MB)
long fileSize = filePart.getSize();
long maxSize = 5 * 1024 * 1024; // 5MB
if (fileSize > maxSize) {
response.sendRedirect("index.jsp?message=文件大小不能超过 5MB&type=error");
return;
}
// 6. 生成唯一文件名,防止覆盖
String uniqueFileName = System.currentTimeMillis() + "_" + fileName;
// 7. 将文件保存到服务器
// 使用 Java NIO 的 Files.copy() 方法,它更高效
Path uploadFilePath = Paths.get(uploadPath, uniqueFileName);
// 使用 try-with-resources 确保 InputStream 被正确关闭
try (InputStream input = filePart.getInputStream()) {
Files.copy(input, uploadFilePath, StandardCopyOption.REPLACE_EXISTING);
}
// 8. 上传成功,重定向到首页并显示成功消息
// 注意:重定向后,request 中的数据会丢失,所以通过 URL 参数传递消息
response.sendRedirect("index.jsp?message=文件 " + uniqueFileName + " 上传成功&type=success");
} catch (Exception e) {
// 处理异常
e.printStackTrace();
response.sendRedirect("index.jsp?message=上传失败: " + e.getMessage() + "&type=error");
}
}
/**
* 获取提交的文件名,兼容不同浏览器
*/
private String getSubmittedFileName(Part part) {
for (String cd : part.getHeader("content-disposition").split(";")) {
if (cd.trim().startsWith("filename")) {
String fileName = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
return fileName.substring(fileName.lastIndexOf('/') + 1).substring(fileName.lastIndexOf('\\') + 1);
}
}
return null;
}
/**
* 检查文件扩展名是否为图片
*/
private boolean isImage(String extension) {
if (extension == null || extension.isEmpty()) {
return false;
}
// 转换为小写进行比较
String ext = extension.toLowerCase();
return ext.equals("jpg") || ext.equals("jpeg") || ext.equals("png") || ext.equals("gif") || ext.equals("bmp");
}
}
代码关键点解析:
@WebServlet("/upload"):将 Servlet 映射到/uploadURL。@MultipartConfig:必须添加,这个注解告诉 Servlet 容器,这个 Servlet 将处理multipart/form-data请求,它还会配置一些默认的内存和临时文件设置。request.getPart("imageFile"):这是 Servlet 3.0+ 提供的标准 API,用于获取表单中名为imageFile的文件部分。Part对象代表了上传的一个文件。- 文件名处理:
getSubmittedFileName()方法用于从Part对象中提取原始文件名,不同浏览器(如 Chrome, IE)提供的文件名格式不同,这个方法做了兼容性处理。 - 文件验证:
- 类型验证:通过检查文件扩展名(如
jpg,png)来判断是否为图片。注意:这种方法不是100%安全的,因为恶意用户可以伪造文件扩展名,更严格的验证方式是读取文件的“魔数”(Magic Number),即文件头部的几个字节,来判断其真实类型,但对于大多数应用场景,检查扩展名已经足够。 - 大小验证:通过
part.getSize()获取文件大小(以字节为单位),并与预设的最大值进行比较。
- 类型验证:通过检查文件扩展名(如
- 生成唯一文件名:使用
System.currentTimeMillis()时间戳作为前缀,可以极大地避免因文件名相同而导致的文件覆盖问题。 - 文件保存:
getServletContext().getRealPath("")获取当前 Web 应用的根目录在服务器上的物理路径。- 我们创建了一个
uploads目录来存放所有上传的文件。 Files.copy()是 Java NIO 提供的高效文件复制方法,比传统的InputStream+OutputStream更简洁、更健壮。
- 响应:使用
response.sendRedirect()进行重定向,将用户带回首页,并通过 URL 参数?message=...&type=...传递操作结果,前端 JavaScript 会读取这些参数并显示相应的消息。
第四步:配置 web.xml (可选)
如果你不使用 @WebServlet 注解,或者需要进行更复杂的配置(如过滤器、监听器),你需要在 WEB-INF/web.xml 文件中配置 Servlet。

<?xml version="1.0" encoding="UTF-8"?>
<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>UploadServlet</servlet-name>
<servlet-class>com.yourpackage.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/upload</url-pattern>
</servlet-mapping>
</web-app>
第五步:部署和测试
- 将项目部署到你的 Web 服务器(如 Apache Tomcat)。
- 启动服务器。
- 在浏览器中访问
http://localhost:8080/你的项目名/index.jsp。 - 选择一张图片文件,点击“上传”。
- 查看结果:
- 如果成功,页面会显示“上传成功”的消息。
- 如果失败(不是图片文件或文件太大),页面会显示相应的错误消息。
- 检查你的项目部署目录下(
Tomcat/webapps/你的项目名/),是否多了一个uploads文件夹,里面是否存放了你上传的图片。
高级与生产环境考虑
- 数据库存储:在生产环境中,通常不会将图片文件本身存入数据库,而是将图片的路径或URL存入数据库,数据库中可以有一个表,如
images,包含字段id,file_name,file_path,upload_date等。 - 文件服务器:为了提高性能和可扩展性,上传的文件最好存储在专门的文件服务器(如 MinIO, AWS S3, 阿里云 OSS)上,而不是应用服务器的本地磁盘,这样可以将应用服务器和存储服务器解耦。
- 安全性:
- 路径遍历攻击:在上传文件时,要小心用户上传的文件名可能包含 这样的路径,可能导致文件被保存到
uploads目录之外,一个简单的做法是只保留文件名,去掉所有路径信息。 - 病毒扫描:对上传的文件进行病毒扫描,防止恶意文件上传。
- 权限控制:确保
uploads目录对 Web 服务器进程有写入权限,但对外部访问者没有读取权限(如果需要通过 URL 访问,则另当别论)。
- 路径遍历攻击:在上传文件时,要小心用户上传的文件名可能包含 这样的路径,可能导致文件被保存到
- 性能:对于非常大的文件,Servlet 3.0 的默认配置可能不够用,你可以在
@MultipartConfig中自定义配置,@MultipartConfig( fileSizeThreshold = 1024 * 1024, // 1 MB 内存阈值,超过则写入临时文件 maxFileSize = 1024 * 1024 * 10, // 10 MB 最大单个文件大小 maxRequestSize = 1024 * 1024 * 15 // 15 MB 最大请求大小 (包含所有文件和表单数据) )
