在 WebWork(Struts 2 前身)中,文件上傳並非在 Action 執行時才開始,而是在請求進入 ServletDispatcher 的早期階段就已完成。整個流程可概括為四個關鍵步驟:
請求首先到達 ServletDispatcher.service(HttpServletRequest, HttpServletResponse) 方法。此處調用 wrapRequest(request) 將原始請求包裝,以便後續處理多部分表單數據。
在 wrapRequest 中,框架通過 MultiPartRequest.isMultiPart(request) 檢查是否為 multipart/form-data 請求。若是,則創建 MultiPartRequestWrapper 實例,傳入保存目錄(saveDir)與最大尺寸(maxSize)參數,並返回該包裝後的請求對象。
構造器內部邏輯分為兩階段:
webwork.properties 獲取 webwork.multipart.parser 屬性。若未配置,預設使用 PellMultiPartRequest 並記錄警告;也支援舊式簡寫 pell、cos、jakarta 分別映射到對應實現類。Class.forName() 載入解析器類,驗證其繼承自 MultiPartRequest,然後調用構造器 (HttpServletRequest, String, int) 創建實例,賦值給內部 multi 字段。若配置使用 Jakarta 解析器,則進入 JakartaMultiPartRequest 構造器:
DiskFileUpload,設置 setSizeThreshold(0) 強制所有內容寫入磁盤臨時文件(因 WebWork API 缺乏流式處理能力)。upload.parseRequest(servletRequest) 解析請求,將每個表單字段與上傳文件均生成獨立臨時文件(如 upload_00000017.tmp)。解析完成後,控制權返回 ServletDispatcher.wrapRequest,隨後進入 serviceAction 開啟 Action 與攔截器棧調用。
當 Action 棧開始執行時,文件已全部寫入臨時目錄。開發者在 Action 或攔截器中通過 MultiPartRequestWrapper.getFiles("fieldName") 即可獲取 File 數組進行後續業務處理(如複製至永久路徑)。
臨時文件採用 upload_XXXXXXXX.tmp 格式,序號由上傳順序決定。高併發場景下,無法預知某用戶上傳文件對應的臨時文件名,導致實現上傳進度條極其困難(除非修改源碼或擴展框架)。
WebWork 採用「兩次拷貝」:第一次從請求輸入流寫入臨時文件,第二次從臨時文件複製到目標路徑。這在簡化 API 設計的同時,帶來了額外 I/O 開銷。對於大文件場景,建議評估是否直接使用流式處理庫(如 Apache Commons FileUpload 的 Streaming API)繞過臨時文件。
非上傳文件的臨時文件(如普通表單字段)會在某個時刻被刪除,但具體清理時間點不固定(有時極早、有時極晚、甚至遺留),推測可能有守護線程定期掃描。生產環境需監控臨時目錄磁盤佔用,必要時編寫定時清理任務。
webwork.properties 中指定 webwork.multipart.parser=jakarta(或 cos),避免預設 Pell 解析器的已知缺陷。webwork.multipart.maxSize 限制上傳大小,防止惡意佔用磁盤。File 對象後,儘快移動至業務目錄並清理引用,減少臨時文件殘留風險。