Apache Commons FileUpload 是 Java Web 文件上傳的經典組件,雖然 Servlet 3.0+ 提供了原生 @MultipartConfig 支援,但理解 FileUpload 核心 API 對維護老項目、適配非標準容器、實現自定義解析邏輯仍有價值。本文提供最小依賴、最簡代碼的完整上傳範例,涵蓋工廠配置、流式解析、UUID 命名、大小限制、異常處理。
<dependencies>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
<html>
<body>
<h2>文件上傳演示</h2>
<form action="upload" method="post" enctype="multipart/form-data">
<p>選擇文件: <input type="file" name="file" /></p>
<p><input type="submit" value="上傳" /></p>
</form>
</body>
</html>
@WebServlet("/upload")
public class FileUploadServlet extends HttpServlet {
private static final String UPLOAD_DIR = "C:/temp/uploads"; // Windows 路徑示例
private static final long MAX_FILE_SIZE = 1024 * 1024; // 1MB
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
try {
// 1. 創建解析器
ServletFileUpload upload = new ServletFileUpload();
upload.setFileSizeMax(MAX_FILE_SIZE);
// 也可設置總請求大小: upload.setSizeMax(10 * 1024 * 1024);
// 2. 獲取迭代器 (低內存模式,適合大文件)
FileItemIterator iterator = upload.getItemIterator(req);
while (iterator.hasNext()) {
FileItemStream item = iterator.next();
if (!item.isFormField()) {
// 3. 處理文件流
handleFileItem(item);
} else {
// 處理普通表單字段 (可選)
String fieldName = item.getFieldName();
String value = Streams.asString(item.openStream(), StandardCharsets.UTF_8);
System.out.println("表單字段: " + fieldName + " = " + value);
}
}
// 4. 轉發成功頁面
req.getRequestDispatcher("success.jsp").forward(req, resp);
} catch (FileUploadException e) {
throw new ServletException("文件上傳解析失敗", e);
}
}
private void handleFileItem(FileItemStream item) throws IOException {
String originalName = item.getName();
System.out.println("接收文件: " + originalName);
// 1. 生成唯一文件名 (UUID + 原擴展名)
String ext = "";
int dot = originalName.lastIndexOf('.');
if (dot > 0) ext = originalName.substring(dot);
String savedName = UUID.randomUUID().toString() + ext;
// 2. 確保目錄存在
File dir = new File(UPLOAD_DIR);
if (!dir.exists()) dir.mkdirs();
File target = new File(dir, savedName);
// 3. 流式寫入 (4KB 緩衝區)
byte[] buffer = new byte[4096];
try (InputStream in = item.openStream();
OutputStream out = new BufferedOutputStream(new FileOutputStream(target))) {
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
System.out.println("文件保存至: " + target.getAbsolutePath());
}
}
<web-app version="2.5" xmlns="https://java.sun.com/xml/ns/javaee">
<servlet>
<servlet-name>FileUploadServlet</servlet-name>
<servlet-class>com.example.FileUploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FileUploadServlet</servlet-name>
<url-pattern>/upload</url-pattern>
</servlet-mapping>
</web-app>
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<body>
<h3>✅ 文件上傳成功!</h3>
<p><a href="upload.html">繼續上傳</a></p>
</body>
</html>
| 參數 | 設置方法 | 默認值 | 建議值 |
|---|---|---|---|
| 單文件最大值 | upload.setFileSizeMax() | 無限制 | 10-100MB |
| 總請求最大值 | upload.setSizeMax() | 無限制 | 單文件 × 最大文件數 |
| 內存閾值 | factory.setSizeThreshold() | 10KB | 1-5MB |
| 臨時目錄 | factory.setRepository() | java.io.tmpdir | 項目外專用目錄 |
| 文件名編碼 | upload.setHeaderEncoding() | 平台默認 | UTF-8 |
// 模式一:List parseRequest(req) - 全量載入內存
List<FileItem> items = upload.parseRequest(req);
for (FileItem item : items) {
item.write(new File(target)); // 一次性寫入
}
// 適用:小文件、高併發、需多次讀取流
// 模式二:FileItemIterator getItemIterator(req) - 流式處理 (本文採用)
FileItemIterator it = upload.getItemIterator(req);
while (it.hasNext()) {
FileItemStream item = it.next();
try (InputStream in = item.openStream()) { ... }
}
// 適用:大文件、低內存、單次處理
try {
FileItemIterator it = upload.getItemIterator(req);
// ...
} catch (FileUploadException e) {
if (e instanceof SizeLimitExceededException) {
out.println("❌ 文件大小超過限制 (" + MAX_FILE_SIZE/1024/1024 + "MB)");
} else if (e instanceof FileSizeLimitExceededException) {
out.println("❌ 單個文件超過限制");
} else {
out.println("❌ 上傳失敗: " + e.getMessage());
}
e.printStackTrace();
}
@MultipartConfig + request.getPart() 原生 API,零依賴MultipartFile + spring.servlet.multipart 配置Commons FileUpload 核心流程:ServletFileUpload 解析器 → FileItemIterator 流式迭代 → 區分表單/文件字段 → InputStream 流式寫入目標存儲。本文示例採用迭代器模式,內存佔用恆定 (4KB 緩衝區),適合大文件上傳場景。關鍵生產級細節:UUID 防重名、編碼設置防亂碼、大小限制防攻擊、臨時目錄隔離、異常分類處理。理解這套機制,能輕鬆遷移至現代框架或實現自定義存儲邏輯。