这个教程将覆盖从最基础的表单上传到更现代的 AJAX 无刷新上传,并包含进度条、后端验证、文件大小限制等常见场景。

整体思路
-
前端 (HTML + jQuery + AJAX):
- 创建一个包含文件输入框的 HTML 表单。
- 使用 jQuery 监听表单的提交事件(或按钮的点击事件)。
- 阻止表单的默认提交行为(避免页面刷新)。
- 使用
FormData对象来封装文件数据和其他表单字段。 - 通过
$.ajax()将FormData发送到 Java 后端。 - (可选)监听上传进度事件,显示进度条。
- 接收并处理后端返回的 JSON 响应,提示用户上传成功或失败。
-
后端 (Java + Servlet):
- 创建一个 Servlet 来处理
/upload这样的 URL 请求。 - 使用
Part对象(Servlet 3.0+ API)来接收前端传来的文件。 - 获取文件名、文件大小、输入流等信息。
- 进行后端校验(如文件类型、文件大小)。
- 将文件保存到服务器的指定目录(如
uploads文件夹)。 - 返回一个 JSON 响应给前端,告知上传结果(成功/失败、文件信息等)。
- 创建一个 Servlet 来处理
准备工作
- 项目环境:
- 一个标准的 Java Web 项目(使用 Maven 或 Gradle)。
- 一个 Servlet 容器(如 Tomcat, Jetty)。
- 依赖库:
- jQuery: 在你的 HTML 页面中引入 jQuery 库。
- Servlet API: 如果你使用 Maven,它会自动管理,如果你是手动搭建,确保
servlet-api.jar在你的WEB-INF/lib目录下。 - JSON 库: 为了方便返回 JSON 格式的数据,推荐使用
Jackson或Gson,这里我们以 Jackson 为例。
Maven pom.xml 依赖示例:
<dependencies>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- Jackson for JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>
创建上传目录
在你的 Web 项目的 webapp 目录下,创建一个用于存放上传文件的文件夹,webapp/uploads。

重要: 为了让 Tomcat 等容器能写入这个目录,你需要确保 uploads 文件夹有正确的写入权限,一个更健壮的做法是在服务器启动时,由代码在服务器的文件系统(如 webapps/your-project/WEB-INF/uploads)中创建并管理此目录。
后端 Java 代码 (Servlet)
创建一个 FileUploadServlet.java 文件。
import com.fasterxml.jackson.databind.ObjectMapper;
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.HashMap;
import java.util.Map;
@WebServlet("/upload")
@MultipartConfig(
fileSizeThreshold = 1024 * 1024 * 2, // 2 MB
maxFileSize = 1024 * 1024 * 10, // 10 MB
maxRequestSize = 1024 * 1024 * 15 // 15 MB
)
public class FileUploadServlet extends HttpServlet {
private static final String UPLOAD_DIR = "uploads"; // 上传目录相对路径
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 设置响应内容类型为 JSON
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
Map<String, Object> result = new HashMap<>();
try {
// 2. 获取上传的文件 Part
Part filePart = request.getPart("file"); // "file" 必须与前端 input 的 name 属性一致
if (filePart == null) {
result.put("success", false);
result.put("message", "没有选择文件");
response.getWriter().write(objectMapper.writeValueAsString(result));
return;
}
// 3. 获取文件名并进行安全处理(防止路径遍历攻击)
String fileName = getFileName(filePart);
String applicationPath = getServletContext().getRealPath("");
String uploadFilePath = applicationPath + File.separator + UPLOAD_DIR;
// 确保上传目录存在
File uploadDir = new File(uploadFilePath);
if (!uploadDir.exists()) {
uploadDir.mkdir();
}
// 4. 构建文件在服务器上的完整路径
String fullPath = uploadFilePath + File.separator + fileName;
// 5. 保存文件
filePart.write(fullPath);
// 6. 返回成功响应
result.put("success", true);
result.put("message", "文件上传成功");
result.put("fileName", fileName);
result.put("filePath", UPLOAD_DIR + "/" + fileName); // 返回相对路径,方便前端访问
} catch (IllegalStateException e) {
// 文件大小超过限制
result.put("success", false);
result.put("message", "文件大小超过限制: " + e.getMessage());
} catch (Exception e) {
// 其他错误
e.printStackTrace();
result.put("success", false);
result.put("message", "上传失败: " + e.getMessage());
}
// 7. 将结果以 JSON 格式返回给前端
response.getWriter().write(objectMapper.writeValueAsString(result));
}
/**
* 从 Part 对象中提取文件名,处理不同浏览器的兼容性问题
*/
private String getFileName(final Part part) {
final String partHeader = part.getHeader("content-disposition");
for (String content : partHeader.split(";")) {
if (content.trim().startsWith("filename")) {
String fileName = content.substring(content.indexOf('=') + 1).trim().replace("\"", "");
// 为了防止文件名冲突,可以在这里添加唯一前缀,如 UUID
// return UUID.randomUUID().toString() + "-" + fileName;
return fileName;
}
}
return null;
}
}
代码解释:
@WebServlet("/upload"): 定义 Servlet 的访问 URL。@MultipartConfig: 至关重要,这个注解告诉 Servlet 容器这个 Servlet 将处理multipart/form-data请求(即文件上传),它还设置了文件大小的限制:fileSizeThreshold: 当文件大小超过此值时,内容会被临时存储到磁盘上,否则存在内存中。maxFileSize: 单个文件的最大允许大小。maxRequestSize: 整个请求(包含所有文件和表单数据)的最大允许大小。
request.getPart("file"): 获取名为file的文件部分。name必须和前端的<input name="file">一致。getFileName(): 一个辅助方法,用于从 HTTP 头中安全地提取文件名。filePart.write(fullPath): 将上传的文件内容写入到服务器指定的路径。ObjectMapper: 用于将 Java Map 对象轻松转换为 JSON 字符串发送给前端。
前端 HTML 和 jQuery 代码
创建一个 index.html 文件。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">jQuery 文件上传示例</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; }
.progress { margin-top: 10px; }
.progress-bar {
height: 20px;
background-color: #f0f0f0;
border-radius: 10px;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background-color: #5cb85c;
width: 0%;
transition: width 0.3s;
}
#result { margin-top: 20px; padding: 10px; border-radius: 5px; display: none; }
.success { background-color: #dff0d8; color: #3c763d; }
.error { background-color: #f2dede; color: #a94442; }
</style>
</head>
<body>
<div class="container">
<h2>jQuery 文件上传</h2>
<form id="uploadForm">
<div>
<label for="fileInput">选择文件:</label>
<input type="file" id="fileInput" name="file" required>
</div>
<button type="submit">上传文件</button>
</form>
<!-- 进度条 -->
<div class="progress" style="display: none;">
<div class="progress-bar">
<div class="progress-bar-fill" id="progressBarFill"></div>
</div>
</div>
<!-- 结果显示区域 -->
<div id="result"></div>
</div>
<!-- 引入 jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(document).ready(function() {
$('#uploadForm').on('submit', function(event) {
// 1. 阻止表单默认提交行为
event.preventDefault();
// 2. 获取表单数据
var formData = new FormData(this);
// formData 会自动包含 name="file" 的文件输入框
// 3. 显示进度条
$('.progress').show();
$('#result').hide().removeClass('success error');
// 4. 发送 AJAX 请求
$.ajax({
url: 'upload', // 对应 Servlet 的 @WebServlet("/upload")
type: 'POST',
data: formData,
// 必须设置为 false,因为 FormData 对象本身会处理 Content-Type
processData: false,
// 必须设置为 false,因为 jQuery 默认会为 processData=false 的数据设置 'application/x-www-form-urlencoded'
contentType: false,
// 上传进度监听
xhr: function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable) {
var percent = Math.round((e.loaded / e.total) * 100);
$('#progressBarFill').css('width', percent + '%');
}
}, false);
return xhr;
},
// 请求成功回调
success: function(response) {
$('.progress').hide();
$('#progressBarFill').css('width', '0%');
if (response.success) {
$('#result').text('上传成功! 文件名: ' + response.fileName)
.addClass('success').show();
} else {
$('#result').text('上传失败: ' + response.message)
.addClass('error').show();
}
},
// 请求失败回调
error: function(jqXHR, textStatus, errorThrown) {
$('.progress').hide();
$('#progressBarFill').css('width', '0%');
var errorMsg = "请求失败: " + textStatus;
if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
errorMsg = jqXHR.responseJSON.message;
}
$('#result').text(errorMsg)
.addClass('error').show();
}
});
});
});
</script>
</body>
</html>
前端代码解释:
event.preventDefault(): 核心,阻止表单的默认提交,从而实现 AJAX 无刷新上传。new FormData(this):this指向表单元素。FormData会自动收集表单中所有name属性的字段,包括文件,这是处理文件上传最标准的方式。processData: false和contentType: false: 必须设置。processData: false阻止 jQuery 将FormData对象转换为查询字符串。contentType: false阻止 jQuery 自动设置Content-Type头,让浏览器使用正确的multipart/form-data格式和边界(boundary)。xhr.upload.addEventListener('progress', ...): 这是实现上传进度条的关键,我们通过自定义xhr对象来监听上传过程中的progress事件。success和error回调: 分别处理服务器返回的成功和失败情况,我们解析返回的 JSON 数据,并更新页面内容给用户反馈。
总结与扩展
你将 FileUploadServlet.class 和 index.html(以及 uploads 目录)部署到你的服务器上,访问 index.html 就可以测试文件上传了。
常见扩展和最佳实践:
-
文件重命名: 为了避免文件名冲突和安全隐患(如恶意文件名),强烈建议在后端保存文件时进行重命名。
// 在 getFileName 方法中或保存前 String originalName = getFileName(filePart); String fileExtension = originalName.substring(originalName.lastIndexOf(".")); String newFileName = UUID.randomUUID().toString() + fileExtension; String fullPath = uploadFilePath + File.separator + newFileName; filePart.write(fullPath); -
文件类型校验:
- 前端校验: 使用
<input accept=".jpg,.png,.gif">可以让浏览器只选择特定类型的文件,但可以被轻易绕过。 - 后端校验: 必须在后端进行校验,可以通过检查文件名后缀或更可靠地读取文件内容(如 "magic number")来判断。
// 在 getFileName 方法后添加 String fileName = getFileName(filePart); String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); List<String> allowedExtensions = Arrays.asList("jpg", "jpeg", "png", "gif"); if (!allowedExtensions.contains(fileExtension)) { result.put("success", false); result.put("message", "不支持的文件类型"); // ... 返回错误 }
- 前端校验: 使用
-
多文件上传:
- 前端: 将
<input type="file">改为<input type="file" multiple>。 - 后端: 使用
request.getParts()获取所有Part对象,然后遍历处理。// 在 doPost 方法中 Collection<Part> parts = request.getParts(); for (Part part : parts) { if (part.getName().equals("file")) { // 或者根据你的字段名判断 // 处理每个文件... } }
- 前端: 将
-
数据库记录: 上传文件后,你还需要在数据库中记录这个文件的信息(如文件名、路径、上传者、上传时间等),以便后续管理和引用,你可以在
filePart.write(fullPath)成功后,向数据库插入一条记录。
这个完整的示例为你提供了一个从基础到进阶的 jQuery + Java 文件上传解决方案,涵盖了大多数实际开发中的需求。
