首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Servlet 文件上傳完整實現:Apache Commons FileUpload 核心用法與進度條實戰

     編輯:Java 綜合教學

前言

文件上傳是 Web 開發高頻需求。Servlet 3.0 前需借助第三方庫,Apache Commons FileUpload 是經典選擇。本文提供兩個版本完整實現:基礎版(單文件/多文件/表單混合上傳)與 進階版(流式寫入+實時進度顯示),並解析核心 API 與生產環境關鍵配置。

一、核心依賴

<dependencies>
    <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>
</dependencies>

二、基礎版:標準文件上傳 Servlet

package com.zhanghaobo.fileupload;

import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

public class UploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        // 1. 創建磁盤工廠:配置臨時文件目錄、內存閾值
        DiskFileItemFactory factory = new DiskFileItemFactory();
        String realPath = req.getServletContext().getRealPath("/upload"); // 獲取 Web 絕對路徑
        factory.setRepository(new File(realPath));        // 臨時文件存放目錄
        factory.setSizeThreshold(1024 * 1024);            // 1MB 內存閾值,超過寫磁盤
        
        // 2. 創建上傳處理器
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setHeaderEncoding("UTF-8");                // 解決中文文件名亂碼
        upload.setFileSizeMax(100 * 1024 * 1024);         // 單文件最大 100MB
        upload.setSizeMax(500 * 1024 * 1024);             // 總請求最大 500MB
        
        try {
            // 3. 解析請求,獲取 FileItem 列表
            List list = upload.parseRequest(req);
            
            for (FileItem item : list) {
                String fieldName = item.getFieldName();
                
                if (item.isFormField()) {
                    // 普通表單字段
                    String value = item.getString("UTF-8");
                    req.setAttribute(fieldName, value);
                } else {
                    // 文件字段
                    String originalName = item.getName();
                    // 處理瀏覽器差異:IE 包含完整路徑,Chrome/FF 僅文件名
                    String fileName = originalName.substring(originalName.lastIndexOf("\\") + 1);
                    req.setAttribute(fieldName, fileName);
                    
                    // 4. 寫入目標文件(FileItem 提供便捷 write 方法)
                    File targetFile = new File(realPath, fileName);
                    item.write(targetFile); // 內部自動處理內存/臨時文件轉移
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            req.setAttribute("error", "上傳失敗: " + e.getMessage());
        }
        
        // 5. 轉發結果頁
        req.getRequestDispatcher("showResult.jsp").forward(req, resp);
    }
}

核心 API 解析

類/方法作用關鍵點
DiskFileItemFactoryFileItem 創建工廠setRepository(臨時目錄)、setSizeThreshold(內存閾值)
ServletFileUpload請求解析核心parseRequest() 返回 List、支持文件大小限制
FileItem表單項抽象(文件/普通字段)isFormField() 區分類型、getString(encoding) 讀取文本、write(File) 保存文件

三、進階版:流式寫入 + 實時進度顯示

public class UploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        DiskFileItemFactory factory = new DiskFileItemFactory();
        String realPath = req.getServletContext().getRealPath("/upload");
        factory.setRepository(new File(realPath));
        factory.setSizeThreshold(1024 * 1024);
        
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setHeaderEncoding("UTF-8");
        
        try {
            List list = upload.parseRequest(req);
            
            for (FileItem item : list) {
                String fieldName = item.getFieldName();
                
                if (item.isFormField()) {
                    String value = item.getString("UTF-8");
                    req.setAttribute(fieldName, value);
                } else {
                    String originalName = item.getName();
                    String fileName = originalName.substring(originalName.lastIndexOf("\\") + 1);
                    req.setAttribute(fieldName, fileName);
                    
                    // === 進階:手動流式寫入 + 進度計算 ===
                    try (InputStream is = item.getInputStream();
                         OutputStream os = new FileOutputStream(new File(realPath, fileName))) {
                        
                        long totalSize = item.getSize();        // 文件總大小
                        byte[] buffer = new byte[4096];         // 4KB 緩衝區
                        long uploaded = 0;
                        int len;
                        DecimalFormat df = new DecimalFormat("#.00");
                        
                        while ((len = is.read(buffer)) != -1) {
                            os.write(buffer, 0, len);
                            uploaded += len;
                            // 實時輸出進度(生產環境可推送 WebSocket/前端輪詢)
                            System.out.printf("總大小: %d 字節 | 進度: %s%% | 已上傳: %d 字節%n", 
                                totalSize, df.format((double) uploaded / totalSize * 100), uploaded);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        req.getRequestDispatcher("showResult.jsp").forward(req, resp);
    }
}

進階版關鍵差異

  • 流式讀寫:使用 item.getInputStream() 獲取輸入流,手動控制緩衝區寫入
  • 進度計算:利用 item.getSize() 獲取總大小,累加 read() 返回值計算實時進度
  • 適用場景:大文件上傳進度條、斷點續傳預檢、上傳速度限流
  • 注意:item.write() 內部已處理臨時文件清理,手動流式寫入需自行確保異常時清理臨時文件

四、前端表單關鍵點

<form action="upload" method="post" enctype="multipart/form-data">
    <input type="text" name="username" placeholder="用戶名" /><br/>
    <input type="file" name="file1" /><br/>
    <input type="file" name="file2" /><br/>
    <input type="submit" value="上傳" />
</form>

三要素:method="post"、enctype="multipart/form-data"、input type="file"

五、生產環境關鍵配置清單

配置項推薦值說明
臨時目錄/tmp/upload 或 專用磁盤分區避免佔用系統盤、支持大文件、定期清理策略
內存閾值1-5 MB過小頻繁寫磁盤,過大佔用 Heap,根據平均文件大小調優
最大文件大小業務實際上限 (如 100MB)防止惡意大文件攻擊、OOM
最大請求大小單文件上限 × 並發文件數防止多文件同時上傳撐爆內存
文件名衝突處理UUID + 原始後綴 / 時間戳 + 隨機數避免覆蓋、支持同名文件並存
文件類型校驗MIME 類型 + 魔數檢測 + 後綴白名單防止腳本上傳、偽裝文件攻擊
權限控制上傳目錄禁用執行權限 (chmod -x)防止上傳 WebShell 執行
病毒掃描集成 ClamAV / 商業反病毒 API高安全級別系統必備

六、Servlet 3.0+ 原生支持(補充)

Servlet 3.0 (Tomcat 7+, Jetty 8+) 內置文件上傳支持,無需第三方庫:

@WebServlet("/upload")
@MultipartConfig(
    location = "/tmp/upload",           // 臨時文件目錄
    fileSizeThreshold = 1024 * 1024,    // 內存閾值 1MB
    maxFileSize = 100 * 1024 * 1024,    // 100MB
    maxRequestSize = 500 * 1024 * 1024  // 500MB
)
public class NativeUploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        // 獲取 Part(對應 FileItem)
        Part filePart = req.getPart("file1");
        String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString();
        
        // 保存文件
        filePart.write(Paths.get("/upload", fileName).toString());
        
        // 獲取普通字段
        String username = req.getParameter("username");
    }
}

遷移建議:新項目優先使用 Servlet 3.0 原生 API,減少依賴;維護舊項目或需精細控制上傳流程(如進度、斷點續傳)繼續用 Commons FileUpload。

七、結語

文件上傳看似簡單,實則涉及內存管理、磁盤 I/O、安全防護、用戶體驗等多維工程考量。掌握 Commons FileUpload 核心流程(工廠→解析器→FileItem→流處理),理解內存閾值與臨時文件機制,具備流式寫入進度計算能力,並建立生產環境配置清單,才能構建健壯、安全、用戶友好的上傳功能。建議團隊封裝通用 FileUploadUtils 工具類,統一異常處理、文件命名、安全校驗、進度回調,實現「業務代碼零感知上傳」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved