| 上傳組件 | 特點 | 現狀 |
|---|---|---|
| Apache Commons FileUpload | 標準、輕量、社群活躍、Spring MVC 底層實作 | 推薦使用 |
| SmartUpload | 早期商業組件、功能豐富 | 已停止維護 |
| Servlet 3.0 Part API | 標準 API、無第三方依賴 | 功能較簡單 |
| COS (com.oreilly.servlet) | 早期經典 | 已停止維護 |
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="myfile" />
<input type="submit" value="上傳" />
</form>
三個必要條件:
method="POST"enctype="multipart/form-data"<input type="file">DiskFileUpload diskFileUpload = new DiskFileUpload(); (舊版) 或 new ServletFileUpload(factory) (新版)List<FileItem> list = diskFileUpload.parseRequest(request); 解析請求item.isFormField() 區分一般欄位與檔案item.getInputStream() 讀取流 → FileOutputStream 寫入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 FileUploadServlet extends HttpServlet {
// 配置常數
private static final String UPLOAD_DIR = "uploads"; // 相對路徑
private static final String TEMP_DIR = "uploads/temp"; // 暫存目錄
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
private static final long MAX_REQUEST_SIZE = 50 * 1024 * 1024; // 50MB
private static final int MEMORY_THRESHOLD = 4 * 1024; // 4KB
private String realBasePath;
@Override
public void init() throws ServletException {
realBasePath = getServletContext().getRealPath("/");
// 初始化目錄
new File(realBasePath, UPLOAD_DIR).mkdirs();
new File(realBasePath, TEMP_DIR).mkdirs();
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 1. 驗證 multipart
if (!ServletFileUpload.isMultipartContent(request)) {
out.println("錯誤:請使用 multipart/form-data 編碼");
return;
}
// 2. 建立工廠
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(MEMORY_THRESHOLD);
factory.setRepository(new File(realBasePath, TEMP_DIR));
// 3. 建立解析器
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setFileSizeMax(MAX_FILE_SIZE);
upload.setSizeMax(MAX_REQUEST_SIZE);
upload.setHeaderEncoding("UTF-8"); // 解決中文檔名
try {
// 4. 解析請求
List<FileItem> items = upload.parseRequest(request);
String savedFilePath = null;
String description = "";
for (FileItem item : items) {
if (item.isFormField()) {
// 一般表單欄位
if ("description".equals(item.getFieldName())) {
description = item.getString("UTF-8");
}
} else {
// 檔案欄位
String fileName = item.getName();
if (fileName == null || fileName.isEmpty()) continue;
// 安全檔名處理
String originalName = new File(fileName).getName();
String ext = "";
int dotIdx = originalName.lastIndexOf('.');
if (dotIdx > 0) ext = originalName.substring(dotIdx);
String savedName = UUID.randomUUID().toString() + ext;
File savedFile = new File(new File(realBasePath, UPLOAD_DIR), savedName);
// 5. 寫入檔案 (自動清理暫存)
item.write(savedFile);
savedFilePath = UPLOAD_DIR + "/" + savedName;
out.println("檔案已儲存: " + savedFile.getAbsolutePath() + "<br/>");
}
}
// 顯示結果
out.println("<h3>上傳成功</h3>");
if (savedFilePath != null) {
out.println("<a href='" + request.getContextPath() + "/" + savedFilePath + "' target='_blank'>檢視檔案</a><br/>");
}
out.println("說明: " + description);
} catch (FileUploadBase.FileSizeLimitExceededException e) {
out.println("錯誤:單一檔案超過 " + (MAX_FILE_SIZE/1024/1024) + "MB 限制");
} catch (FileUploadBase.SizeLimitExceededException e) {
out.println("錯誤:總上傳大小超過 " + (MAX_REQUEST_SIZE/1024/1024) + "MB 限制");
} catch (Exception e) {
e.printStackTrace();
out.println("錯誤:" + e.getMessage());
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().println("請使用 POST 方法上傳檔案");
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>檔案上傳</title>
<style>
body { font-family: sans-serif; max-width: 600px; margin: 2rem auto; }
.form-group { margin-bottom: 1rem; }
label { display: block; margin-bottom: 0.5rem; font-weight: bold; }
input[type="file"] { border: 1px solid #ccc; padding: 0.5rem; width: 100%; }
button { background: #007bff; color: white; border: none; padding: 0.75rem 1.5rem; cursor: pointer; }
button:hover { background: #0056b3; }
.result { margin-top: 2rem; padding: 1rem; background: #f8f9fa; border-radius: 4px; }
</style>
</head>
<body>
<h2>檔案上傳測試</h2>
<form action="/upload" method="post" enctype="multipart/form-data">
<div class="form-group">
<label>檔案說明</label>
<input type="text" name="description" placeholder="選填" />
</div>
<div class="form-group">
<label>選擇檔案 (最大 10MB)</label>
<input type="file" name="myfile" required />
</div>
<button type="submit">上傳檔案</button>
</form>
<div class="result" id="result" style="display:none;"></div>
</body>
</html>
<web-app xmlns="https://xmlns.jcp.org/xml/ns/javaee" version="3.1">
<servlet>
<servlet-name>FileUploadServlet</servlet-name>
<servlet-class>com.example.upload.FileUploadServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>FileUploadServlet</servlet-name>
<url-pattern>/upload</url-pattern>
</servlet-mapping>
</web-app>
sizeThreshold (預設 10KB) 時,寫入暫存目錄DiskFileItemFactory.setRepository(File) 指定暫存目錄item.write(File) 會自動刪除暫存檔,手動用 getInputStream() 則需自行清理// 方案一:Commons FileUpload 1.3.1+ 內建支援
upload.setHeaderEncoding("UTF-8");
// 方案二:手動轉碼 (舊版本)
String fileName = item.getName();
String decodedName = new String(fileName.getBytes("ISO-8859-1"), "UTF-8");
| 設定 | 範圍 | 異常類型 |
|---|---|---|
| upload.setFileSizeMax() | 單一檔案 | FileSizeLimitExceededException |
| upload.setSizeMax() | 整個請求 | SizeLimitExceededException |
捕獲異常時請求流已讀完,可直接回應錯誤頁面。
@Configuration
public class MultipartConfig {
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setMaxUploadSize(50 * 1024 * 1024); // 50MB
resolver.setMaxUploadSizePerFile(10 * 1024 * 1024); // 10MB
resolver.setDefaultEncoding("UTF-8");
return resolver;
}
}
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 50MB
file-size-threshold: 2KB
location: /tmp/uploads # 暫存目錄
Apache Commons FileUpload 是 Java 檔案上傳的基石組件,掌握其核心流程 (工廠→解析器→FileItem→寫入) 與關鍵配置 (暫存、大小、編碼) 至關重要。現代 Spring Boot 專案建議直接使用 MultipartFile 抽象層,配合 CommonsMultipartResolver 或 StandardServletMultipartResolver,享受聲明式配置與自動綁定便利。無論何種架構,安全驗證、暫存管理、檔名處理永遠是不可省略的核心環節。