本文提供四個層層遞進的 Commons FileUpload 實作範例:從最基本的單檔解析、多檔上傳、大小限制,到完整的 Servlet 封裝與 web.xml 配置,涵蓋實務開發最常見的需求場景。
commons-fileupload-1.2.jar (或更新版 1.4+)commons-io-1.3.2.jar (FileUpload 依賴)WEB-INF/lib 目錄<form action="demo1.jsp" method="post" enctype="multipart/form-data">
您的姓名: <input type="text" name="username"><br>
上傳檔案: <input type="file" name="myfile"><br>
<input type="submit" value="送出">
</form>
<%@ page import="org.apache.commons.fileupload.*" %>
<%@ page import="org.apache.commons.fileupload.servlet.*" %>
<%@ page import="org.apache.commons.fileupload.disk.*" %>
<%@ page import="java.util.*" %>
<%
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (isMultipart) {
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) {
// 一般欄位
if ("username".equals(item.getFieldName())) {
out.print("姓名: " + item.getString() + "<br>");
}
} else {
// 檔案欄位
out.print("上傳檔名: " + item.getName() + "<br>");
}
}
} else {
out.print("請設定 enctype='multipart/form-data'");
}
%>
<form action="demo2.jsp" method="post" enctype="multipart/form-data">
檔案1: <input type="file" name="myfile"><br>
檔案2: <input type="file" name="myfile"><br>
<input type="submit" value="上傳">
</form>
<%@ page import="org.apache.commons.fileupload.*" %>
<%@ page import="org.apache.commons.fileupload.servlet.*" %>
<%@ page import="org.apache.commons.fileupload.disk.*" %>
<%@ page import="java.util.*, java.io.*" %>
<%
String uploadPath = "D:/uploads";
File dir = new File(uploadPath);
if (!dir.exists()) dir.mkdirs();
if (ServletFileUpload.isMultipartContent(request)) {
try {
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (!item.isFormField()) {
String fileName = item.getName();
if (fileName != null) {
File fullFile = new File(fileName);
File savedFile = new File(uploadPath, fullFile.getName());
item.write(savedFile);
}
}
}
out.print("上傳成功");
} catch (Exception e) {
e.printStackTrace();
}
}
%>
<%@ page import="org.apache.commons.fileupload.*" %>
<%@ page import="org.apache.commons.fileupload.servlet.*" %>
<%@ page import="org.apache.commons.fileupload.disk.*" %>
<%@ page import="java.util.*, java.io.*" %>
<%
File uploadDir = new File("D:/uploads");
File tempDir = new File("D:/uploads/temp");
uploadDir.mkdirs(); tempDir.mkdirs();
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(4096); // 4KB 記憶體緩衝
factory.setRepository(tempDir); // 暫存目錄
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setSizeMax(4 * 1024 * 1024); // 4MB 總大小限制
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (!item.isFormField() && item.getName() != null) {
File saved = new File(uploadDir, new File(item.getName()).getName());
item.write(saved);
}
}
out.print("上傳成功");
%>
package com.example.upload;
import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class UploadServlet extends HttpServlet {
private String uploadPath = "D:/uploads";
private String tempPath = "D:/uploads/temp";
private File tempDir;
@Override
public void init() throws ServletException {
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) uploadDir.mkdirs();
tempDir = new File(tempPath);
if (!tempDir.exists()) tempDir.mkdirs();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(4096);
factory.setRepository(tempDir);
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setSizeMax(4 * 1024 * 1024); // 4MB
try {
List<FileItem> items = upload.parseRequest(req);
for (FileItem item : items) {
if (!item.isFormField()) {
String fileName = item.getName();
if (fileName != null) {
File saved = new File(uploadPath, new File(fileName).getName());
item.write(saved);
}
}
}
System.out.println("上傳成功");
} catch (Exception e) {
e.printStackTrace();
// 生產環境應導向錯誤頁面
}
}
}
<form action="fileupload" method="post" enctype="multipart/form-data">
檔案: <input type="file" name="myfile"><br>
<input type="submit" value="上傳">
</form>
<servlet>
<servlet-name>UploadServlet</servlet-name>
<servlet-class>com.example.upload.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/fileupload</url-pattern>
</servlet-mapping>
| 類別/方法 | 用途 | 關鍵參數 |
|---|---|---|
| DiskFileItemFactory | 建立 FileItem 工廠 | setSizeThreshold(記憶體閾值), setRepository(暫存目錄) |
| ServletFileUpload | 解析 multipart 請求 | parseRequest(request) → List<FileItem> |
| FileItem | 代表單一表單項目 | isFormField(), getName(), getString(), write(File), getInputStream() |
| upload.setFileSizeMax() | 單檔大小限制 | bytes, 拋出 FileSizeLimitExceededException |
| upload.setSizeMax() | 請求總大小限制 | bytes, 拋出 SizeLimitExceededException |
| upload.setHeaderEncoding() | 解決中文檔名亂碼 | "UTF-8" (1.3.1+ 版本支援) |
// 副檔名檢查
String fileName = item.getName().toLowerCase();
if (!fileName.matches(".*\\.(jpg|jpeg|png|gif|pdf)$")) {
throw new IllegalArgumentException("不允許的檔案類型");
}
// MIME 類型檢查
String contentType = item.getContentType();
if (!Arrays.asList("image/jpeg", "image/png", "application/pdf").contains(contentType)) {
throw new IllegalArgumentException("MIME 類型不符");
}
// Magic Number 檢查 (最可靠)
byte[] header = new byte[8];
try (InputStream in = item.getInputStream()) { in.read(header); }
boolean isJpeg = header[0]==(byte)0xFF && header[1]==(byte)0xD8;
boolean isPng = header[0]==(byte)0x89 && header[1]==(byte)0x50;
if (!isJpeg && !isPng) throw new IllegalArgumentException("檔案頭不符");
// 策略一:UUID + 原始副檔名
String savedName = UUID.randomUUID() + "." + FilenameUtils.getExtension(fileName);
// 策略二:時間戳 + 隨機數
String savedName = System.currentTimeMillis() + "_" + new Random().nextInt(10000) + "." + ext;
// 策略三:MD5 雜湊 + 副檔名 (去重)
String md5 = DigestUtils.md5Hex(item.getInputStream());
String savedName = md5 + "." + ext;
// 方案一:日期分層
String datePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
// 儲存路徑: /uploads/2024/01/15/uuid.jpg
// 方案二:檔名雜湊分桶
int hash = fileName.hashCode();
int dir1 = hash & 0xF; // 0-15
int dir2 = (hash >> 4) & 0xF; // 0-15
String hashPath = dir1 + "/" + dir2;
// 儲存路徑: /uploads/3/A/uuid.jpg (256 個子目錄)
try {
List<FileItem> items = upload.parseRequest(request);
// 處理邏輯...
} catch (FileUploadBase.FileSizeLimitExceededException e) {
// 單檔超過限制
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "檔案過大: " + e.getMessage());
} catch (FileUploadBase.SizeLimitExceededException e) {
// 總大小超過限制
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "總上傳量過大: " + e.getMessage());
} catch (FileUploadException e) {
// 解析錯誤 (非 multipart、連線中斷等)
log.error("上傳解析失敗", e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "上傳處理異常");
} catch (Exception e) {
log.error("未預期錯誤", e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "系統錯誤");
}
| 方案 | 優點 | 缺點 | 建議場景 |
|---|---|---|---|
| Commons FileUpload + Servlet | 零依賴框架、完全控制、效能好 | 代碼較繁瑣、需手動管理暫存 | 純 Servlet 專案、微服務輕量節點 |
| Spring MVC MultipartFile | 自動綁定、聲明式配置、整合驗證 | 依賴 Spring 生態 | Spring Boot/Spring MVC 專案 (首選) |
| Servlet 3.0 @MultipartConfig | 標準 API、無第三方依賴 | 功能較簡單、大檔案處理較弱 | Java EE 標準相容、不想引入額外 JAR |
| 雲端直傳 (OSS/S3 Presigned URL) | 減輕應用伺服器壓力、高併發 | 架構複雜、需前端配合 | 大檔案、高併發、雲原生架構 |
Commons FileUpload 是 Java 檔案上傳的經典解決方案,四個範例涵蓋了從原型驗證到生產部署的完整演進路徑。實務開發中建議:
MultipartFile 簡化開發