Apache common-fileupload用戶指南

Apache common-fileupload用戶指南

使用FILE UPLOAD

File Upload有很多不同的使用方式,取決於你的應用程序,最簡單的方式是使用一個單獨的方法來轉換servlet請求,然後處理這些像你的應用發出請求的項。在另一個面,你可能需要決定對哪個項執行完全控制以保存它。比如:你可能會決定把內容保存到數據庫中。

這裏,我們將描述File Upload的原理,並舉例說明一些較爲簡單--最常使用的模式,關於File Upload的定製,可以在其他其他位置。

File Upload依賴於Commons IO,請確保你的classpath裏有對於版本的jar包(相關頁面有提及)。

如何工作

一個file upload請求包含一組根據RFC 1867編碼的已排序的項目,“基於HTML表單的文件上傳”。File Upload能夠轉換一個請求然後爲你的應用程序提供一組單一上傳的項目。每個這樣的項目都實現了FileItem接口,不太在乎如何具體實現。

這一頁介紹了commons fileuplaod庫的傳統API,傳統的API是一個方便的方法。但是,爲了最終性能着想,你可能更喜歡使用Streaming API。

每一個文件項都有一些你的應用程序可能感性趣的屬性。比如,所有項都有一個名字和內容類型,並且提供了一個輸入流去訪問它的數據。另一方面,你可能需要使用不同的方式去處理這些項,依據該項是否一個正則的表單元素(數據是否來自於一個傳統的文本框或者類似的表單元素),或者一個上傳的文件。文件項接口提供了方法去實現這個檢測,以及相應的方式訪問它們的數據。

Servlet和門戶組件

從1.1版開始,FileUpload在servlet和門戶組件兩種環境裏提供文件上傳請求。用法在兩種環境裏差不多,所以這篇文檔餘下的部分只涉及servlet環境。

如果你正在創建一個門戶組件應用,你應該區分下面兩點和這篇文檔的不同之處。

在任何地方看到ServletFileUpload類的參考文獻,請用PortletFileUpload類來代替。

在任何地方看到HttpServletRequest 類的參考文獻,請用ActionRequest類來代替。

解析請求

當然,在你操作上傳組件之前,你需要解析請求自身。確保請求實際上是一個文件上傳請求很簡單,File Upload的提供了一個靜態方法使它非常簡單。

// Check that we have a file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);

現在我們可以把請求解析成它的組成項。

最簡單的情況

最簡單的使用情況如下:

只要上傳項足夠小,那就應該保存在內存裏。

大的項應該被寫入硬盤的臨時文件文件。

非常大的請求應該被阻止上傳。

你的應用默認設置可以保存在內存中的最大大小的項目,最大可上傳的請求,以及臨時存放上傳文件的臨時目錄是可被接受的。

沒有比下面更簡單的處理請求的方法了:

// 爲基於硬盤文件的項目集創建一個工廠

FileItemFactory factory = new DiskFileItemFactory();
// 創建一個新的文件上傳處理器
ServletFileUpload upload = new ServletFileUpload(factory);
// 解析請求
List items = upload.parseRequest(request);

以上就是所有需要做的。真的!

請求被解析後,你會提到一些由實現了FileItem接口的項目組成的集合。我們會在下面討論這此項目。

運用更多控制

如果你的使用情況和最簡單的使用情況差不多,如上所述,但是,如果你需要更多的控制的話,可以選擇定義上傳處理器和項目工廠的行爲,這是委簡單的。

// 爲基於硬盤文件的項目集創建一個工廠

FileItemFactory factory = new DiskFileItemFactory();
// 設置工廠的約束條件
factory.setSizeThreshold(yourMaxMemorySize);
factory.setRepository(yourTempDirectory);
// 創建一個新的文件上傳處理器
ServletFileUpload upload = new ServletFileUpload(factory);

// 設置可請求在最大可上傳數據量
upload.setSizeMax(yourMaxRequestSize);
// 解析請求
List items = upload.parseRequest(request);

當然,每個配置都可心獨立存在,不過你可以一次性配置工廠的所有配置項,使用一個替代的構造方法可以做到這點,像這樣:

// 爲基於硬盤文件的項目集創建一個工廠

DiskFileItemFactory factory = new DiskFileItemFactory(
yourMaxMemorySize, yourTempDirectory);

如果你需要相比於解析請求更多的控制,像把項目存到其他地方,比如存到數據庫,你需要閱讀customizing File Upload。

處理上傳項目

當完成請求解析後,你會得到一個由你想要處理的文件項組成的集合。大部分情況下,你會想要把使用文件上傳的上傳的項處理方法區別於和常規表單處理,你需要這樣做:

 // 處理上傳項目
Iterator iter = items.iterator();
while (iter.hasNext()) {
    FileItem item = (FileItem) iter.next();

    if (item.isFormField()) {
        processFormField(item);
    } else {
        processUploadedFile(item);
    }
}

對於一個正常的表單項,你只會關心它的name,以及對應的字符串值。和你的預期一樣,訪問上傳項目也非常簡單。

 // 處理一個正常表單
if (item.isFormField()) {
    String name = item.getFieldName();
    String value = item.getString();
    ...
}

對於一個文件上傳項,在你處理它的內容之前,你可能會想要知道幾件事情。這裏有一個關於你可能感興趣的一些方法的例子。

 // 處理上傳項目
if (!item.isFormField()) {
    String fieldName = item.getFieldName();
    String fileName = item.getName();
    String contentType = item.getContentType();
    boolean isInMemory = item.isInMemory();
    long sizeInBytes = item.getSize();
    ...
}

對於上傳的文件,你通常不會想把它放到內存中,除非它很小,或者你沒有其它可替代的方法。恰恰相反,你會把文件當成流來處理,並把整個文件存放到最終位置。File Upload提供了簡單的方法來實現這兩種處理。

 // 處理上傳項目
if (writeToFile) {
    File uploadedFile = new File(...);
    item.write(uploadedFile);
} else {
    InputStream uploadedStream = item.getInputStream();
    ...
    uploadedStream.close();
}

注意,在FileUpload的默認實現裏,如果數據已經在臨時文件中,write方法會試圖更改存放到指定目的地的文件名。其實數據的複製只會在由於某種原因重命名失敗的時候纔會成功,或者當數據在內存中已經存在的時候。

如果你需要訪問內存中的數據,你僅僅只需要使用get()方法將數據獲取並保存到一個字節數組中。

 // 處理內存中的上傳項
byte[] data = item.get();
...

資源清理

本節只在你使用DiskFileItem時適用。換句話說,它只適用於你在處理上傳項目之前將它們寫入臨時文件的情況。這些臨時文件在不使用(更準確的說,是如果java.io.File的類似實例被垃圾回收了。)的情況下會自動刪除。org.apache.commons.io.FileCleaner類會默默的完成這個操作,它會啓動一個收割機線程。

這個收割機線程在不使用的時候應該關閉。在一個servlet環境裏,使用一個特殊的servlet context listener FileCleanerCleanup來完成這件事情。爲了讓它起作用,需要在你的web.xml裏面加入下面的代碼片段:

 <web-app>
  ...
  <listener>
    <listener-class>
      org.apache.commons.fileupload.servlet.FileCleanerCleanup
    </listener-class>
  </listener>
  ...
</web-app>

創建一個硬盤文件項工廠

FileCleanerCleanup提供了一個org.apache.commons.io.FileCleaningTracker的實例,在創建org.apache.commons.fileupload.disk.DiskFileItemFactory的時候一定要使用這個實例。下面的代碼裏的方法調用實現了這個步驟。

    public static DiskFileItemFactory newDiskFileItemFactory(ServletContext context,
                                                             File repository) {
        FileCleaningTracker fileCleaningTracker
            = FileCleanerCleanup.getFileCleaningTracker(context);
        DiskFileItemFactory factory
            = new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD,
                                      repository);
        factory.setFileCleaningTracker(fileCleaningTracker);
        return factory;
    }

禁用的清除臨時文件

要禁用跟蹤臨時文件,你要設置FileCleaningTracker爲空。因爲置空,創建的文件將不會再被跟蹤,特別說明,它們也不會再被自動刪除。

與病毒掃描器互動

病毒掃描器可能和使用了FileUpload的應用程序運行在同一系統上,這可能會導致web容器執行一些異常的行爲。這一節介紹了一些你可能會遇到的異常行爲,並且提供了一些方法去處理這些問題。

FileUpload的默認實現會將超出臨界值的數據寫入硬盤。一旦這個文件關閉,系統上的任何病毒掃描器就是被喚醒並且檢測這個文件,可能會隔離這個文件--將它移動到不會引起問題的特定地方。這個,當然,對於程序開發員來說,由於上傳的文件項目將不再訪問,會讓程序員感到奇怪。而另一方面,處於臨界值之下的數據會被加載到內存中,因此不會被病毒掃描器檢測。這允許病毒以某種形式保存在內存中(儘管它曾經被寫入到硬盤中,然後被病毒掃描器掃描並隔離)。

一種常用的解決辦法是在系統上設置一個目錄來專門保存上傳的文件。並且設置病毒掃描器忽略這個目錄。這樣可以保證應用下的文件不會被分離,但是會分離程序開發人員和病毒掃描的責任關係。可以提供一個擴展的處理程序來執行病毒掃描,將無毒的,被殺過毒的文件移動到特定的位置,或者集成一個病毒掃描系統到應用內部。

關於擴展一個處理以及集成一個掃描系統到應用內部的詳細內容超出了本文檔的範圍。

上傳進度

如果希望上傳非常大的文件,你需要抽向你的用戶提供反饋,已經上傳了多少。HTML頁面允許通過返回一個multipart/replace響應來實現一個進度條,或其它類似的東西。

可以通過使用一個progress listener來供應一個進度條:

 //創建一個進度條監聽器
ProgressListener progressListener = new ProgressListener(){
   public void update(long pBytesRead, long pContentLength, int pItems) {
       System.out.println("We are currently reading item " + pItems);
       if (pContentLength == -1) {
           System.out.println("So far, " + pBytesRead + " bytes have been read.");
       } else {
           System.out.println("So far, " + pBytesRead + " of " + pContentLength
                              + " bytes have been read.");
       }
   }
};
upload.setProgressListener(progressListener);

幫你自己一個忙,像上面一樣實現你的第一個進度條監聽器,因爲它向你展示了一個缺陷:進步條使用非常頻繁。依賴於servlet引擎和其他環境工廠,它可能會被任何網絡數據包調用。換句話說,你的進度條監聽器會引起性能問題!可能是一個典型的解決方案,是減少進步條的活動數。比如,只有當上傳了1兆字節的時候才反饋給用戶:

 //創建一個進度監聽器
ProgressListener progressListener = new ProgressListener(){
   private long megaBytes = -1;
   public void update(long pBytesRead, long pContentLength, int pItems) {
       long mBytes = pBytesRead / 1000000;
       if (megaBytes == mBytes) {
           return;
       }
       megaBytes = mBytes;
       System.out.println("We are currently reading item " + pItems);
       if (pContentLength == -1) {
           System.out.println("So far, " + pBytesRead + " bytes have been read.");
       } else {
           System.out.println("So far, " + pBytesRead + " of " + pContentLength
                              + " bytes have been read.");
       }
   }
};

下一步是什麼?

希望這篇文章能夠給你在應用中使用File Upload提供了一個好的想法。想要更多關於這篇文章中提到的方法的資料,和其它方法一樣,你需要參考JavaDocs

這裏描述的使用應滿足大部分的文件上傳的需要。然而,如果你有更復雜的要求,FileUpload還能以其靈活的定製幫助你。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章