[轉載]Jakarta Commons FileUpload 用戶指南
Posted on 2007-04-23 19:56 ZelluX 閱讀(1524) 評論(1) 編輯 收藏 所屬分類: OOP本文是分享文檔站長胡蘿卜的作品。大家可以自由閱讀、在網絡上進行分發,前提是必須保留本文檔的完整性。
分享文檔是一家專業的Java技術網站,給中國Java 程序員提供各種Java資源如文檔,工具,教程,社區交流等。
我們的官方網站是:http://chinesedocument.com
我們的官方論壇是:http://bbs.chinesedocument.com
請大家多多關注分享文檔,我們還會發布更多優秀的文檔!
第1章 使用FileUpload
FileUpload能以多種方式使用,這取決于你的應用需求。舉個簡單的例子,你可能調用一個單獨的方法來解析servelt的請求,并且處理那些項目。 從另一個方面來講,你可能想自定義FileUpload來完全控制個別項目的存貯;例如,你想流化那些內容,并存到數據庫里去。
這里我們會介紹使用FileUpload的基礎原則,并描述一些簡單的通用的使用模式。我們會在在其它地方介紹關于FileUpload的自定義。
FileUpload依賴于一些公用的IO,因此,要確保在你繼續之前,你的classpath里已經有依賴頁面里提起的那些版本。
第2章 FileUpload如何工作?
一個上傳請求由一系列根據RFC1867("Form-based File Upload in HTML".)編碼的項目列表組成。FileUpload可以解析這樣的請求,并為你的應用提供那些已上傳的項目的列表。每一個這樣的項目都實現了FielItem接口,我們不用管它們的底層實現。
這個頁面描述了commons fileupload庫的常用API。這些常用API是非常方便的途徑。然而,為了最好的性能,你可能更喜歡最快的Streaming API 。
每一個文件項目有一些自己的屬性,這些屬性也許正是你的應用程序感興趣的地方。例如,每個項目有個一個名字和內容類型,并且可以提供一個輸入流來訪問它們的數據。另一方面來看,你可能需要用不同方式來處理不同的項目,這就依賴于那些項目是否是一個正常的表單字域,也就是說,這些數據來自于一個普通的文本框或類似HTML的字域,還是一個要上傳文件字段。FileItem接口提供一些方法來做這樣一個決定,并且用最合適的方法訪問這些數據。
FileUpload使用FileItemFactory創建一個新的文件項目。這將會給FileUpload最好的靈活性。工廠最終控制每個項目如何被創建。默認的工廠在內存或者硬盤里存儲項目的數據,這依賴于項目的大小(例如,有多少字節的數據。)。不過,為了適用于你的應用,你還是可以自定義這種行為的。
第3章 servlets and portlets
從V1.1版開始,FileUpload就開始支持servlet和portlet的文件上傳請求。這兩種環境的用法基本上差不多,因此,文檔的剩下部分都將是在servlet環境里。
如果你正在構建一個portet應用,那么下面兩個差別是你在讀文檔時應注意的:
你在哪里引用了ServletFileUpload類,就用PortletFileUpload類來替代它。
你在哪里引用了HttpServletRequest類,就用ActionRequest類替代它。
第4章 解析請求
在你同那些上傳的項目一起工作前,你需要先解析請求本身。以確保這個請求確實是一個文件上傳請求。FileUpload是通過調用一個靜態方法來實現的。
// Check that we have a file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
現在,我們已經準備好解析請求里的項目了。
4.1 最簡單的例子
下面是一些簡單的使用場景:
?? 上傳項目只要足夠小,就應該保留在內存里。
?? 較大的項目應該被寫在硬盤的臨時文件上。
?? 非常大的上傳請求應該避免。
?? 限制項目在內存中所占的空間,限制最大的上傳請求,并且設定臨時文件的位置。
處理這個場景的請求很簡單:
// Create a factory for disk-based file items
FileItemFactory factory = new DiskFileItemFactory();
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// Parse the request
List /* FileItem */ items = upload.parseRequest(request);
這就是我們所需要的全部代碼了!
解析的結果就是一個項目的List,每個項目都實現了FileItem接口。我們將在下面討論如何處理這些項目。
4.2 訓練如何控制
如果你的使用場景和上面那個簡單的例子很接近,但是你又需要一點點控制,那么你可以很容易地定義upload處理器或者文件項目工廠的行為。下面這個例子顯示了幾個配置選項。
// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory();
// Set factory constraints
factory.setSizeThreshold(yourMaxMemorySize);
factory.setRepository(yourTempDirectory);
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// Set overall request size constraint
upload.setSizeMax(yourMaxRequestSize);
// Parse the request
List /* FileItem */ items = upload.parseRequest(request);
當然,每個配置方法是獨立于其它任意一個的。但是如果你想一次性配置他們,你可以用parseRequest()的另一個重載方法,像這樣:
// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory(
yourMaxMemorySize, yourTempDirectory);
如果你還想使用更多的控制,比如存儲項目到其它地方(如,數據庫),那么你可以看FileUpload自定義介紹。
第5章 處理上傳的項目
一旦解析完成,那么你會得到一個待處理的文件項目列表。很多的情況下,你會想用不同的方式來處理文件上傳域和正常的表單域,因此,你可以這樣做:
// Process the uploaded items
Iterator iter = items.iterator();
while (iter.hasNext()) {
FileItem item = (FileItem) iter.next();
if (item.isFormField()) {
processFormField(item);
} else {
processUploadedFile(item);
}
}
對于普通的表單域來說,你可能對項目的名稱和字符型值 很感興趣。就像你希望的那樣,照下面的做:
// Process a regular form field
if (item.isFormField()) {
String name = item.getFieldName();
String value = item.getString();
...
}
對于上傳文件,這里就有很多不同啦~你可能想知道更多其它的內容。下面是個例子,里面包含了不少你感興趣的方法。
// Process a file upload
if (!item.isFormField()) {
String fieldName = item.getFieldName();
String fileName = item.getName();
String contentType = item.getContentType();
boolean isInMemory = item.isInMemory();
long sizeInBytes = item.getSize();
...
}
對于上傳的文件,你肯定不希望總是通過內存來訪問它,除非它很小,或者你實在沒有別的選擇余地了。你很希望使用流來處理文件內容或者將文件保存到它的最終位置。FileUpload提供簡單的方式來完成兩方面的需求。
// Process a file upload
if (writeToFile) {
File uploadedFile = new File(...);
item.write(uploadedFile);
} else {
InputStream uploadedStream = item.getInputStream();
...
uploadedStream.close();
}
注意:在FileUpload的默認實現中wirte()方法應該值得關注,如果數據還在臨時文件里沒有移除,那么這個方法就會試圖重命名這個文件為相應的目標文件。事實上如果重命名失敗了的話,數據就僅僅被拷貝。
如果你需要訪問內存中的上傳數據,你可以用get()方法來獲得數據的二進制數組形式。
// Process a file upload in memory
byte[] data = item.get();
...
第6章 清除資源
這一節只適用于你使用了DiskFileItem。換句話說,它只適用于你在處理上傳文件之前將上傳文件寫入過臨時文件這種情形。
像這種臨時文件會被自動刪除, 如果它們不再被使用(更確切地說,java.io.File的實例已經被GC掉了。 )這是由org.apache.commons.io.FileCleaner類在后臺完成的,它會啟動一個收割機線程。
這個收割機線程在它不再被需要時會被停止。在servlet環境里,這是通過指定一個名叫FileCleanerCleanup的servlet上下文監聽器來實現的。要做到這里,在你的web.xml增加下面的代碼:
<web-app>
...
<listener>
<listener-class>
org.apache.commons.fileupload.servlet.FileCleanerCleanup
</listener-class>
</listener>
...
</web-app>
不幸的是,事情到這里還沒完。如果你和下面的情況一樣,那么你就只需要按照上面的做,就可以清除資源了。
你使用的是commons-io 1.3或者更晚的版本。
你是從web應用的web-inf/lib里載入commons-io的,并不是從其它位置,如Tomcat的common/lib下。
如果commons-io 1.3是從你的WEB容器的classpath里載入的,那么,下面的情況可能會出現:
建議你運行兩個應用,一個叫A,一個叫B。(這兩個應用可能是完全一樣,只不過上下文名稱不一樣。)這兩個應用都使用了FileCleanerCleanup?,F在,如果你終止應用A,B還在運行,這時,A會終止B的收割機線程。換言之,你要十分仔細地考慮是使用FileCleanerCleanup,還是不使用。
第7章 觀察上傳進度
如果你希望可以上傳很大的文件,這時,你可能想將上傳的狀態告訴用戶,如已經接收了多少。
觀察上傳進度需要通過一個處理監聽器來實現。
//Create a 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);
上面這個監聽器是有問題的。因為它非常頻繁地被調用。這會帶來性能問題。一個比較好的解決辦法是,減少調用。例如,如果megabytes被改變,那么就發出一個消息。
//Create a progress listener
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.");
}
}
};