首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

Spring FTPClient 上傳檔案不完整:Spring 注入與 Main 方法行為差異排查

     編輯:程式設計疑難解答

同一個 `FTPClient.upload()` 方法,在 `main()` 直接呼叫可完整上傳 ZIP 檔,但經由 Spring `@Autowired` 注入的 Service 呼叫卻僅上傳 10 Bytes 的空殼檔案。這類「同碼異果」問題通常源於執行緒上下文差異、資源管理衝突或Spring 代理導致的事務/連線狀態異常。本文提供系統性排查路徑。

核心差異點對照

維度main() 直接呼叫Spring 注入呼叫
執行緒主執行緒Tomcat/容器工作執行緒
連線池無(新建連線)可能共用 DataSource/連線池
事務管理無可能被 `@Transactional` 包裹
異常處理拋出主執行緒可能被全域異常處理器吞沒
檔案流生命週期同步關閉可能被 Spring 代理提前關閉

排查步驟與解決方案

1. 檢查異常是否被吞沒(最常見)


// 在 upload 方法加入完整 try-catch 並記錄日誌
public boolean upload(String ftpPath, String localFilePath) {
    FileInputStream fis = null;
    try {
        fis = new FileInputStream(localFilePath);
        // 關鍵:使用輸入流多載,而非檔案路徑多載
        boolean success = ftpClient.storeFile(ftpPath, fis); 
        return success;
    } catch (IOException e) {
        log.error("FTP 上傳失敗: {}", e.getMessage(), e); // 必須記錄堆疊追蹤
        return false;
    } finally {
        if (fis != null) try { fis.close(); } catch (IOException ignore) {}
    }
}

重點:`storeFile(String, InputStream)` 比 `storeFile(String, String)` 更可控,避免內部二次開啟檔案導致路徑解析差異。

2. 檢查 Spring 事務邊界衝突

  • 若 `upload()` 所在類別/方法標註 `@Transactional`,資料庫事務提交/回滾可能影響 FTP 連線狀態(若共用連線池)。
  • 解決:將 FTP 上傳移出事務邊界,或標註 `@Transactional(propagation = Propagation.NOT_SUPPORTED)`。

3. 驗證 FTPClient 實例是否為單例共用


// 錯誤:單例 Bean 共用同一 FTPClient 實例,多執行緒並發會衝突
@Service
public class FtpService {
    private FTPClient ftpClient = new FTPClient(); // 危險!
}

// 正確:每次操作建立新實例,或使用 ThreadLocal/ObjectPool
@Service
public class FtpService {
    public boolean upload(...) {
        FTPClient client = new FTPClient(); // 方法區域變數,執行緒安全
        try { client.connect(...); client.login(...); 
              return client.storeFile(...); }
        finally { client.disconnect(); }
    }
}

4. 被動模式與防火牆/NAT 穿透


client.enterLocalPassiveMode(); // 必須在 connect/login 之後、storeFile 之前
// 或
client.enterRemotePassiveMode(); // 視伺服器設定

Spring 容器環境(如雲端部署)網路拓撲與本機 `main()` 不同,被動模式設定往往是關鍵。

5. 檢查檔案路徑解析差異

  • `main()` 執行時工作目錄為專案根目錄,相對路徑 `D:/localtmp/...` 正確。
  • Spring 應用部署在 Tomcat/Linux 容器中,工作目錄為 `catalina.base` 或 `/`,相對路徑失效。
  • 解決:使用 `ResourceLoader` 獲取絕對路徑,或配置屬性 `ftp.local.base.path=/data/upload`。

6. 啟用 FTPClient 除錯日誌


// log4j2.xml

    


// 程式碼中
ftpClient.setDebug(true); // 印出原始 FTP 指令/回應

對比 `main()` 與 Spring 環境下的指令流差異(如 `PORT` vs `PASV`、`TYPE I` 等),精準定位問題。

快速驗證清單

  1. ☐ `storeFile` 使用 `InputStream` 多載而非路徑字串。
  2. ☐ `FileInputStream` 在 `finally` 區塊確實關閉。
  3. ☐ FTPClient 非單例共用,每次上傳新建實例。
  4. ☐ 已呼叫 `enterLocalPassiveMode()`。
  5. ☐ 檔案路徑為絕對路徑或經 `ResourceLoader` 解析。
  6. ☐ 無 `@Transactional` 包裹上傳方法,或已排除事務。
  7. ☐ 開啟 DEBUG 日誌對比指令流。

終極替代方案:使用 Spring Integration FTP

若問題持續,改用 Spring 官方整合模組,宣告式處理重試、暫存、錯誤通道:



Spring 環境下的網路/IO 操作,核心差異在於「容器管理的生命週期」與「共用資源的執行緒安全」。逐項排查上述清單,多數「Main 正常、注入異常」問題可解決。

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