Java Web開發人員可使用Apache文件上傳組件來接收瀏覽器上傳的文件,該組件由多個類共同組成,可是,對於使用該組件來編寫文件上傳功能的Java Web開發人員來講,只須要了解和使用其中的三個類:html
DiskFileUpload、FileItem和FileUploadException。
這三個類所有位於org.apache.commons.fileupload包中。java
在準備實驗環境時得到的commons-fileupload-1.0.zip文件的解壓縮目錄中能夠看到一個docs的子目錄,其中包含了Apache文件上傳組件中的各個API類的幫助文檔,從這個文檔中能夠了解到各個API類的使用幫助信息。打開文件上傳組件API幫助文檔中的index.html頁面,在左側分欄窗口頁面中列出了文件上傳組件中的各個API類的名稱。apache
圖1.2數組
讀者不須要逐個去閱讀圖1.2中列出的各個API類的幫助文檔,而應該以圖1.2中的示例代碼爲線索,以其中所使用到的類爲入口點,按圖索驥地進行閱讀,對於示例代碼中調用到的各個API類的方法則應重點掌握。瀏覽器
DiskFileUpload類是Apache文件上傳組件的核心類,應用程序開發人員經過這個類來與Apache文件上傳組件進行交互。但如今Apache建議使用ServletFileUpload類,兩個類的方法相似。下面介紹DiskFileUpload類中的幾個經常使用的重要方法。tomcat
setSizeMax方法用於設置請求消息實體內容的最大容許大小,以防止客戶端故意經過上傳特大的文件來塞滿服務器端的存儲空間,單位爲字節。其完整語法定義以下:服務器
public void setSizeMax(long sizeMax)網絡
若是請求消息中的實體內容的大小超過了setSizeMax方法的設置值,該方法將會拋出FileUploadException異常。app
Apache文件上傳組件在解析和處理上傳數據中的每一個字段內容時,須要臨時保存解析出的數據。由於Java虛擬機默承認以使用的內存空間是有限的(筆者測試不大於100M),超出限制時將會發生「java.lang.OutOfMemoryError」錯誤,若是上傳的文件很大,例如上傳800M的文件,在內存中將沒法保存該文件內容,Apache文件上傳組件將用臨時文件來保存這些數據;但若是上傳的文件很小,例如上傳600個字節的文件,顯然將其直接保存在內存中更加有效。setSizeThreshold方法用於設置是否使用臨時文件保存解析出的數據的那個臨界值,該方法傳入的參數的單位是字節。其完整語法定義以下:jsp
public void setSizeThreshold(int sizeThreshold)
setRepositoryPath方法用於設置setSizeThreshold方法中提到的臨時文件的存放目錄,這裏要求使用絕對路徑。其完整語法定義以下:
public void setRepositoryPath(String repositoryPath)
若是不設置存放路徑,那麼臨時文件將被儲存在"java.io.tmpdir"這個JVM環境屬性所指定的目錄中,tomcat 5.5.9將這個屬性設置爲了「<tomcat安裝目錄>/temp/」目錄。
parseRequest 方法是DiskFileUpload類的重要方法,它是對HTTP請求消息進行解析的入口方法,若是請求消息中的實體內容的類型不是「multipart/form-data」,該方法將拋出FileUploadException異常。parseRequest 方法解析出FORM表單中的每一個字段的數據,並將它們分別包裝成獨立的FileItem對象,而後將這些FileItem對象加入進一個List類型的集合對象中返回。parseRequest 方法的完整語法定義以下:
public List parseRequest(HttpServletRequest req)
parseRequest 方法還有一個重載方法,該方法集中處理上述全部方法的功能,其完整語法定義以下:
parseRequest(HttpServletRequest req,int sizeThreshold,long sizeMax,
String path)
這兩個parseRequest方法都會拋出FileUploadException異常。
isMultipartContent方法方法用於判斷請求消息中的內容是不是「multipart/form-data」類型,是則返回true,不然返回false。isMultipartContent方法是一個靜態方法,不用建立DiskFileUpload類的實例對象便可被調用,其完整語法定義以下:
public static final boolean isMultipartContent(HttpServletRequest req)
因爲瀏覽器在提交FORM表單時,會將普通表單中填寫的文本內容傳遞給服務器,對於文件上傳字段,除了傳遞原始的文件內容外,還要傳遞其文件路徑名等信息,如後面的圖1.3所示。無論FORM表單採用的是「application/x-www-form-urlencoded」編碼,仍是「multipart/form-data」編碼,它們僅僅是將各個FORM表單字段元素內容組織到一塊兒的一種格式,而這些內容又是由某種字符集編碼來表示的。關於瀏覽器採用何種字符集來編碼FORM表單字段中的內容,請參看筆者編著的《深刻體驗java Web開發內幕——核心基礎》一書中的第6.9.2的講解,「multipart/form-data」類型的表單爲表單字段內容選擇字符集編碼的原理和方式與「application/x-www-form-urlencoded」類型的表單是相同的。FORM表單中填寫的文本內容和文件上傳字段中的文件路徑名在內存中就是它們的某種字符集編碼的字節數組形式,Apache文件上傳組件在讀取這些內容時,必須知道它們所採用的字符集編碼,才能將它們轉換成正確的字符文本返回。
對於瀏覽器上傳給WEB服務器的各個表單字段的描述頭內容,Apache文件上傳組件都須要將它們轉換成字符串形式返回,setHeaderEncoding 方法用於設置轉換時所使用的字符集編碼,其原理與筆者編著的《深刻體驗java Web開發內幕——核心基礎》一書中的第6.9.4節講解的ServletRequest.setCharacterEncoding方法相同。setHeaderEncoding 方法的完整語法定義以下:
public void setHeaderEncoding(String encoding)
其中,encoding參數用於指定將各個表單字段的描述頭內容轉換成字符串時所使用的字符集編碼。
注意:若是讀者在使用Apache文件上傳組件時遇到了中文字符的亂碼問題,通常都是沒有正確調用setHeaderEncoding方法的緣由。
FileItem類用來封裝單個表單字段元素的數據,一個表單字段元素對應一個FileItem對象,經過調用FileItem對象的方法能夠得到相關表單字段元素的數據。
FileItem是一個接口,在應用程序中使用的其實是該接口一個實現類,該實現類的名稱並不重要,程序能夠採用FileItem接口類型來對它進行引用和訪問,爲了便於講解,這裏將FileItem實現類稱之爲FileItem類。FileItem類還實現了Serializable接口,以支持序列化操做。
對於「multipart/form-data」類型的FORM表單,瀏覽器上傳的實體內容中的每一個表單字段元素的數據之間用字段分隔界線進行分割,兩個分隔界線間的內容稱爲一個分區,每一個分區中的內容能夠被看做兩部分,一部分是對錶單字段元素進行描述的描述頭,另一部是表單字段元素的主體內容。
主體部分有兩種可能性,要麼是用戶填寫的表單內容,要麼是文件內容。FileItem類對象實際上就是對圖1.3中的一個分區的數據進行封裝的對象,它內部用了兩個成員變量來分別存儲描述頭和主體內容,其中保存主體內容的變量是一個輸出流類型的對象。當主體內容的大小小於DiskFileUpload.setSizeThreshold方法設置的臨界值大小時,這個流對象關聯到一片內存,主體內容將會被保存在內存中。當主體內容的數據超過DiskFileUpload.setSizeThreshold方法設置的臨界值大小時,這個流對象關聯到硬盤上的一個臨時文件,主體內容將被保存到該臨時文件中。臨時文件的存儲目錄由DiskFileUpload.setRepositoryPath方法設置,臨時文件名的格式爲「upload_00000005(八位或八位以上的數字).tmp」這種形式,FileItem類內部提供了維護臨時文件名中的數值不重複的機制,以保證了臨時文件名的惟一性。當應用程序將主體內容保存到一個指定的文件中時,或者在FileItem對象被垃圾回收器回收時,或者Java虛擬機結束時,Apache文件上傳組件都會嘗試刪除臨時文件,以儘可能保證臨時文件能被及時清除。
下面介紹FileItem類中的幾個經常使用的方法:
isFormField方法用於判斷FileItem類對象封裝的數據是否屬於一個普通表單字段,仍是屬於一個文件表單字段,若是是普通表單字段則返回true,不然返回false。該方法的完整語法定義以下:
public boolean isFormField()
getName方法用於得到文件上傳字段中的文件名,對於圖1.3中的第三個分區所示的描述頭,getName方法返回的結果爲字符串「C:\bg.gif」。若是FileItem類對象對應的是普通表單字段,getName方法將返回null。即便用戶沒有經過網頁表單中的文件字段傳遞任何文件,但只要設置了文件表單字段的name屬性,瀏覽器也會將文件字段的信息傳遞給服務器,只是文件名和文件內容部分都爲空,但這個表單字段仍然對應一個FileItem對象,此時,getName方法返回結果爲空字符串"",讀者在調用Apache文件上傳組件時要注意考慮這個狀況。getName方法的完整語法定義以下:
public String getName()
注意:若是用戶使用Windows系統上傳文件,瀏覽器將傳遞該文件的完整路徑,若是用戶使用Linux或者Unix系統上傳文件,瀏覽器將只傳遞該文件的名稱部分。
getFieldName方法用於返回表單字段元素的name屬性值,也就是返回圖1.3中的各個描述頭部分中的name屬性值,例如「name=p1」中的「p1」。getFieldName方法的完整語法定義以下:
public String getFieldName()
write方法用於將FileItem對象中保存的主體內容保存到某個指定的文件中。若是FileItem對象中的主體內容是保存在某個臨時文件中,該方法順利完成後,臨時文件有可能會被清除。該方法也可將普通表單字段內容寫入到一個文件中,但它主要用途是將上傳的文件內容保存在本地文件系統中。其完整語法定義以下:
public void write(File file)
getString方法用於將FileItem對象中保存的主體內容做爲一個字符串返回,它有兩個重載的定義形式:
public java.lang.String getString() public java.lang.String getString(java.lang.String encoding) throws java.io.UnsupportedEncodingException
前者使用缺省的字符集編碼將主體內容轉換成字符串,後者使用參數指定的字符集編碼將主體內容轉換成字符串。若是在讀取普通表單字段元素的內容時出現了中文亂碼現象,請調用第二個getString方法,併爲之傳遞正確的字符集編碼名稱。
getContentType 方法用於得到上傳文件的類型,對於圖1.3中的第三個分區所示的描述頭,getContentType方法返回的結果爲字符串「image/gif」,即「Content-Type」字段的值部分。若是FileItem類對象對應的是普通表單字段,該方法將返回null。getContentType 方法的完整語法定義以下:
public String getContentType()
isInMemory方法用來判斷FileItem類對象封裝的主體內容是存儲在內存中,仍是存儲在臨時文件中,若是存儲在內存中則返回true,不然返回false。其完整語法定義以下:
public boolean isInMemory()
delete方法用來清空FileItem類對象中存放的主體內容,若是主體內容被保存在臨時文件中,delete方法將刪除該臨時文件。儘管Apache組件使用了多種方式來儘可能及時清理臨時文件,但系統出現異常時,仍有可能形成有的臨時文件被永久保存在了硬盤中。在有些狀況下,能夠調用這個方法來及時刪除臨時文件。其完整語法定義以下:
public void delete()
在文件上傳過程當中,可能發生各類各樣的異常,例如網絡中斷、數據丟失等等。爲了對不一樣異常進行合適的處理,Apache文件上傳組件還開發了四個異常類,其中FileUploadException是其餘異常類的父類,其餘幾個類只是被間接調用的底層類,對於Apache組件調用人員來講,只需對FileUploadException異常類進行捕獲和處理便可。
ServletRequestContext類提供訪問request的方法。實現RequestContext接口。
1 package biz; 2 3 import java.io.File; 4 import java.io.UnsupportedEncodingException; 5 import java.util.HashMap; 6 import java.util.Iterator; 7 import java.util.List; 8 import java.util.Map; 9 10 import javax.servlet.ServletException; 11 import javax.servlet.http.HttpServlet; 12 import javax.servlet.http.HttpServletRequest; 13 14 import org.apache.commons.fileupload.FileItem; 15 import org.apache.commons.fileupload.FileUploadException; 16 import org.apache.commons.fileupload.disk.DiskFileItemFactory; 17 import org.apache.commons.fileupload.servlet.ServletFileUpload; 18 19 /** 20 * commons fileupload 包裝類 21 * @author kiant 22 * @version Sep 9, 2008 23 */ 24 public class MutiFileUpload extends HttpServlet { 25 private Map<String, String> parameters; //保存普通 form 表單域 26 private Map<String,FileItem> files; //保存上傳文件 27 28 private String encoding; //設置編碼格式,推薦 jsp 和 處理類 均爲 UTF-8 29 private Long sizeMax; //設置上傳數據的最大數據 30 private int sizeThreshold; //設置內存緩衝區的閥值 31 private String repositoryPath; //文件超出緩衝區大小時的臨時存放目錄 32 33 private String uploadDir; //上傳文件保存文件夾 34 35 36 /* 37 * methods 38 */ 39 /** 40 * 對 request 進行處理 41 * @throws UnsupportedEncodingException 42 */ 43 public void handleRequest(HttpServletRequest request) throws ServletException, UnsupportedEncodingException { 44 parameters = new HashMap<String, String>(); 45 files = new HashMap<String, FileItem>(); 46 47 //DiskFileItem工廠,主要用來設定上傳文件的參數 48 DiskFileItemFactory factory = new DiskFileItemFactory(); 49 factory.setSizeThreshold(getSizeThreshold()); //設置內存緩衝區的閥值 50 if (getRepositoryPath() != null && getRepositoryPath() != "") { 51 factory.setRepository(new File(getRepositoryPath())); //臨時目錄,默認 52 } 53 54 // 使用fileItemFactory爲參數實例化一個ServletFileUpload對象 55 ServletFileUpload upload = new ServletFileUpload(factory); 56 upload.setHeaderEncoding(getEncoding()); //設置編碼格式,推薦 jsp 和 處理類 均爲 UTF-8 57 upload.setSizeMax(getSizeMax()); //設置上傳數據的最大數據 58 59 // 開始處理表單請求 60 try { 61 List items = upload.parseRequest(request); 62 Iterator it = items.iterator(); 63 while (it.hasNext()) { 64 FileItem item = (FileItem) it.next(); 65 if (item.isFormField()) { //若是是表單字段 66 String name = item.getFieldName(); 67 String value = item.getString(getEncoding()); 68 parameters.put(name, value); 69 } else { //若是是文件字段 70 String name = item.getFieldName(); 71 files.put(name, item); 72 } 73 } 74 75 } catch (FileUploadException e) { 76 e.printStackTrace(); 77 } 78 79 } 80 81 82 /* 83 * property accessors 84 */ 85 public Map<String, String> getParameters() { 86 return parameters; 87 } 88 public void setParameters(Map<String, String> parameters) { 89 this.parameters = parameters; 90 } 91 public Map<String, FileItem> getFiles() { 92 return files; 93 } 94 public void setFiles(Map<String, FileItem> files) { 95 this.files = files; 96 } 97 public String getEncoding() { 98 return encoding; 99 } 100 public void setEncoding(String encoding) { 101 this.encoding = encoding; 102 } 103 public Long getSizeMax() { 104 return sizeMax; 105 } 106 public void setSizeMax(Long sizeMax) { 107 this.sizeMax = sizeMax; 108 } 109 public int getSizeThreshold() { 110 return sizeThreshold; 111 } 112 public void setSizeThreshold(int sizeThreshold) { 113 this.sizeThreshold = sizeThreshold; 114 } 115 public String getRepositoryPath() { 116 return repositoryPath; 117 } 118 public void setRepositoryPath(String repositoryPath) { 119 this.repositoryPath = repositoryPath; 120 } 121 122 public String getUploadDir() { 123 return uploadDir; 124 } 125 126 public void setUploadDir(String uploadDir) { 127 this.uploadDir = uploadDir; 128 } 129 130 }
1 ** 2 * Method execute 3 * 4 * @param mapping 5 * @param form 6 * @param request 7 * @param response 8 * @return ActionForward 9 * @throws IOException 10 */ 11 public ActionForward execute(ActionMapping mapping, ActionForm form, 12 HttpServletRequest request, HttpServletResponse response) throws IOException { 13 14 try { 15 //利用組件讀取 "multipart/form-data" 傳輸流文件的具體參數 16 getMutiFileUpload().handleRequest(request); 17 18 String opusName = getMutiFileUpload().getParameters().get("opusName"); 19 String comments = getMutiFileUpload().getParameters().get("comments"); 20 Integer cid = Integer.parseInt(getMutiFileUpload().getParameters().get("categorie0")); 21 int fullWidht = Integer.parseInt(getMutiFileUpload().getParameters().get("fullView")); 22 23 FileItem fileItem = getMutiFileUpload().getFiles().get("picFile"); 24 EcAccount account = (EcAccount)request.getSession().getAttribute("account"); 25 String fileDir = getMutiFileUpload().getUploadDir() + account.getLoginId(); // 保存目錄 26 SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmssS"); 27 String fileName = format.format(new Date()); // 保存文件名 28 fileName = fileDir + "/" + fileName; 29 String[] strType = fileItem.getContentType().split("/"); 30 String extName = strType[1]; //文件擴展名 31 32 //這裏我省略了有效性的驗證 33 34 //填充 opus實體 35 EcOpus opus = new EcOpus(); 36 opus.setEcAccount(account); 37 opus.setEcOpusCategorie(getEcOpusBiz().findCategorieID(cid)); 38 opus.setOpusName(opusName); 39 opus.setOpusComments(comments); 40 opus.setOriginalView(fileName + "o." + extName); 41 opus.setLinkView(fileName + "l." + extName); 42 opus.setSmallView(fileName + "s." + extName); 43 opus.setFullView(fileName + "f." + extName); 44 opus.setSubmitted(new Date()); 45 opus.setImageSize(0); 46 opus.setWidth(0); 47 opus.setHeight(0); 48 opus.setComment(0); 49 opus.setFavourite(0); 50 opus.setTodayView(0); 51 opus.setTotalView(0); 52 opus.setDeleteFlag(Byte.parseByte("0")); 53 54 // 得到寫入路徑 55 String absoluteDir = servlet.getServletContext().getRealPath("/").replaceAll("\\\\", "/"); 56 57 // 開始寫入 58 getEcOpusBiz().addOpus(opus, absoluteDir, fileItem, fullWidht, extName); 59 60 return mapping.findForward("success"); 61 } catch (Exception e) { 62 e.printStackTrace(); 63 return mapping.findForward("fail"); 64 } 65 }
1 // 原文件保存 2 File file = new File(absoluteDir + opus.getOriginalView()); 3 fileItem.write(file);