Apache Common fileUpload API 詳解(轉(zhuǎn))

          轉(zhuǎn)自:http://www.aygfsteel.com/gembin/archive/2008/05/08/199240.html

          文件上傳組件的應(yīng)用與編寫 
          在許多Web站點應(yīng)用中都需要為用戶提供通過瀏覽器上傳文檔資料的功能,例如,上傳郵件附件、個人相片、共享資料等。對文件上傳功能,在 

          瀏覽器端提供了較好的支持,只要將FORM表單的enctype屬性設(shè)置為“multipart/form-data”即可;但在Web服務(wù)器端如何獲取瀏覽器上傳的文 

          件,需要進(jìn)行復(fù)雜的編程處理。為了簡化和幫助Web開發(fā)人員接收瀏覽器上傳的文件,一些公司和組織專門開發(fā)了文件上傳組件。本章將詳細(xì)介 

          紹如何使用Apache文件上傳組件,以及分析該組件源程序的設(shè)計思路和實現(xiàn)方法。 
          1.1 準(zhǔn)備實驗環(huán)境 
          按下面的步驟為本章的例子程序建立運行環(huán)境: 
          (1)在Tomcat 5.5.12的<tomcat的安裝目錄>\webapps目錄中創(chuàng)建一個名為fileupload的子目錄,并在fileupload目錄中創(chuàng)建一個名為test.html 

          的網(wǎng)頁文件,在該文件中寫上“這是test.html頁面的原始內(nèi)容!”這幾個字符。 
          (2)在<tomcat的安裝目錄>\webapps\fileupload目錄中創(chuàng)建一個名為WEB-INF的子目錄,在WEB-INF目錄中創(chuàng)建一個名為classes的子目錄和一個 

          web.xml文件,web.xml文件內(nèi)容如下: 
          <web-app> 
          </web-app> 
          (3)要使用Apache文件上傳組件,首先需要安裝Apache文件上傳組件包。在<tomcat的安裝目錄>\webapps\fileupload\WEB-INF目錄中創(chuàng)建一個 

          名為lib的子目錄,然后從網(wǎng)址http://jakarta.apache.org/commons/fileupload下載到Apache組件的二進(jìn)制發(fā)行包,在本書的附帶帶光盤中也 

          提供了該組件的二進(jìn)制發(fā)行包,文件名為commons-fileupload-1.0.zip。從commons-fileupload-1.0.zip壓縮包中解壓出commons-fileupload 

          -1.0.jar文件,將它放置進(jìn)<tomcat的安裝目錄>\webapps\fileupload\WEB-INF\lib目錄中,就完成了Apache文件上傳組件的安裝。 
          (4)在<tomcat的安裝目錄>\webapps\fileupload目錄中創(chuàng)建一個名為src的子目錄,src目錄用于放置本章編寫的Java源程序。為了便于對 

          Servlet源文件進(jìn)行編譯,在src目錄中編寫一個compile.bat批處理文件,如例程1-1所示。 
          例程1-1 compile.bat 



          set PATH=C:\jdk1.5.0_01\bin;%path% 
          set CLASSPATH=C:\tomcat-5.5.12\common\lib\servlet-api.jar;C:\tomcat-5.5.12\\webapps\ 
          fileupload\WEB-INF\lib\commons-fileupload-1.0.jar;%CLASSPATH% 
          javac -d ..\WEB-INF\classes %1 
          pause 



          在compile.bat批處理文件中要注意將commons-fileupload-1.0.jar文件的路徑加入到CLASSPATH環(huán)境變量中和確保編譯后生成的class文件存放 

          到<tomcat安裝目錄>\webapps\fileupload\WEB-INF\classes目錄中,上面的CLASSPATH環(huán)境變量的設(shè)置值由于排版原因進(jìn)行了換行,實際上不 

          應(yīng)該有換行。接著在src目錄中為compile.bat文件創(chuàng)建一個快捷方式,以后只要在Windows資源管理器窗口中將Java源文件拖動到compile.bat 

          文件的快捷方式上,就可以完成Java源程序的編譯了。之所以要創(chuàng)建compile.bat文件的快捷方式,是因為直接將Java源程序拖動到 

          compile.bat批處理文件時,compile.bat批處理文件內(nèi)編寫的相對路徑不被支持。創(chuàng)建完的fileupload目錄中的文件結(jié)構(gòu)如圖1.1所示。 
          圖1.1 
          (4)啟動Tomcat,在本地計算機(jī)的瀏覽器地址欄中輸入如下地址: 
          http://localhost:8080/fileupload/test.html 
          驗證瀏覽器能夠成功到該網(wǎng)頁文檔。如果瀏覽器無法訪問到該網(wǎng)頁文檔,請檢查前面的操作步驟和改正問題,直到瀏覽器能夠成功到該網(wǎng)頁文 

          檔為止。 
          (5)為了讓/fileupload這個WEB應(yīng)用程序能自動重新裝載發(fā)生了修改的Servlet程序,需要修改Tomcat的server.xml文件,在該文件的<Host>元 

          素中增加如下一個<Context>子元素: 
          <Context path="/fileupload" docBase="fileupload" reloadable="true"/> 
          保存server.xml文件后,重新啟動Tomcat。 

          1.2 Apache文件上傳組件的應(yīng)用 
          Java Web開發(fā)人員可以使用Apache文件上傳組件來接收瀏覽器上傳的文件,該組件由多個類共同組成,但是,對于使用該組件來編寫文件上傳 

          功能的Java Web開發(fā)人員來說,只需要了解和使用其中的三個類:DiskFileUpload、FileItem和FileUploadException。這三個類全部位于 

          org.apache.commons.fileupload包中。 
          1.2.1查看API文檔 
          在準(zhǔn)備實驗環(huán)境時獲得的commons-fileupload-1.0.zip文件的解壓縮目錄中可以看到一個docs的子目錄,其中包含了Apache文件上傳組件中的 

          各個API類的幫助文檔,從這個文檔中可以了解到各個API類的使用幫助信息。打開文件上傳組件API幫助文檔中的index.html頁面,在左側(cè)分欄 

          窗口頁面中列出了文件上傳組件中的各個API類的名稱,在右側(cè)分欄窗口頁面的底部列出了一段示例代碼,如圖1.2所示。 
          圖1.2 
          讀者不需要逐個去閱讀圖1.2中列出的各個API類的幫助文檔,而應(yīng)該以圖1.2中的示例代碼為線索,以其中所使用到的類為入口點,按圖索驥地 

          進(jìn)行閱讀,對于示例代碼中調(diào)用到的各個API類的方法則應(yīng)重點掌握。 
          1.2.2 DiskFileUpload類 
          DiskFileUpload類是Apache文件上傳組件的核心類,應(yīng)用程序開發(fā)人員通過這個類來與Apache文件上傳組件進(jìn)行交互。下面介紹 

          DiskFileUpload類中的幾個常用的重要方法。 
          1.setSizeMax方法 
          setSizeMax方法用于設(shè)置請求消息實體內(nèi)容的最大允許大小,以防止客戶端故意通過上傳特大的文件來塞滿服務(wù)器端的存儲空間,單位為字節(jié) 

          。其完整語法定義如下: 
          public void setSizeMax(long sizeMax) 
          如果請求消息中的實體內(nèi)容的大小超過了setSizeMax方法的設(shè)置值,該方法將會拋出FileUploadException異常。 
          2.setSizeThreshold方法 
          Apache文件上傳組件在解析和處理上傳數(shù)據(jù)中的每個字段內(nèi)容時,需要臨時保存解析出的數(shù)據(jù)。因為Java虛擬機(jī)默認(rèn)可以使用的內(nèi)存空間是有 

          限的(筆者測試不大于100M),超出限制時將會發(fā)生“java.lang.OutOfMemoryError”錯誤,如果上傳的文件很大,例如上傳800M的文件,在 

          內(nèi)存中將無法保存該文件內(nèi)容,Apache文件上傳組件將用臨時文件來保存這些數(shù)據(jù);但如果上傳的文件很小,例如上傳600個字節(jié)的文件,顯然 

          將其直接保存在內(nèi)存中更加有效。setSizeThreshold方法用于設(shè)置是否使用臨時文件保存解析出的數(shù)據(jù)的那個臨界值,該方法傳入的參數(shù)的單 

          位是字節(jié)。其完整語法定義如下: 
          public void setSizeThreshold(int sizeThreshold) 
          3. setRepositoryPath方法 
          setRepositoryPath方法用于設(shè)置setSizeThreshold方法中提到的臨時文件的存放目錄,這里要求使用絕對路徑。其完整語法定義如下: 
          public void setRepositoryPath(String repositoryPath) 
          如果不設(shè)置存放路徑,那么臨時文件將被儲存在"java.io.tmpdir"這個JVM環(huán)境屬性所指定的目錄中,tomcat 5.5.9將這個屬性設(shè)置為了 

          “<tomcat安裝目錄>/temp/”目錄。 
          4. parseRequest方法 
          parseRequest 方法是DiskFileUpload類的重要方法,它是對HTTP請求消息進(jìn)行解析的入口方法,如果請求消息中的實體內(nèi)容的類型不是 

          “multipart/form-data”,該方法將拋出FileUploadException異常。parseRequest 方法解析出FORM表單中的每個字段的數(shù)據(jù),并將它們分別 

          包裝成獨立的FileItem對象,然后將這些FileItem對象加入進(jìn)一個List類型的集合對象中返回。parseRequest 方法的完整語法定義如下: 
          public List parseRequest(HttpServletRequest req) 
          parseRequest 方法還有一個重載方法,該方法集中處理上述所有方法的功能,其完整語法定義如下: 
          parseRequest(HttpServletRequest req,int sizeThreshold,long sizeMax, 
          String path) 
          這兩個parseRequest方法都會拋出FileUploadException異常。 
          5. isMultipartContent方法 
          isMultipartContent方法方法用于判斷請求消息中的內(nèi)容是否是“multipart/form-data”類型,是則返回true,否則返回false。 

          isMultipartContent方法是一個靜態(tài)方法,不用創(chuàng)建DiskFileUpload類的實例對象即可被調(diào)用,其完整語法定義如下: 
          public static final boolean isMultipartContent(HttpServletRequest req) 
          6. setHeaderEncoding方法 
          由于瀏覽器在提交FORM表單時,會將普通表單中填寫的文本內(nèi)容傳遞給服務(wù)器,對于文件上傳字段,除了傳遞原始的文件內(nèi)容外,還要傳遞其 

          文件路徑名等信息,如后面的圖1.3所示。不管FORM表單采用的是“application/x-www-form-urlencoded”編碼,還是“multipart/form-data 

          ”編碼,它們僅僅是將各個FORM表單字段元素內(nèi)容組織到一起的一種格式,而這些內(nèi)容又是由某種字符集編碼來表示的。關(guān)于瀏覽器采用何種 

          字符集來編碼FORM表單字段中的內(nèi)容,請參看筆者編著的《深入體驗java Web開發(fā)內(nèi)幕——核心基礎(chǔ)》一書中的第6.9.2的講解, 

          “multipart/form-data”類型的表單為表單字段內(nèi)容選擇字符集編碼的原理和方式與“application/x-www-form-urlencoded”類型的表單是 

          相同的。FORM表單中填寫的文本內(nèi)容和文件上傳字段中的文件路徑名在內(nèi)存中就是它們的某種字符集編碼的字節(jié)數(shù)組形式,Apache文件上傳組 

          件在讀取這些內(nèi)容時,必須知道它們所采用的字符集編碼,才能將它們轉(zhuǎn)換成正確的字符文本返回。 
          對于瀏覽器上傳給WEB服務(wù)器的各個表單字段的描述頭內(nèi)容,Apache文件上傳組件都需要將它們轉(zhuǎn)換成字符串形式返回,setHeaderEncoding 方 

          法用于設(shè)置轉(zhuǎn)換時所使用的字符集編碼,其原理與筆者編著的《深入體驗java Web開發(fā)內(nèi)幕——核心基礎(chǔ)》一書中的第6.9.4節(jié)講解的 

          ServletRequest.setCharacterEncoding方法相同。setHeaderEncoding 方法的完整語法定義如下: 
          public void setHeaderEncoding(String encoding) 
          其中,encoding參數(shù)用于指定將各個表單字段的描述頭內(nèi)容轉(zhuǎn)換成字符串時所使用的字符集編碼。 
          注意:如果讀者在使用Apache文件上傳組件時遇到了中文字符的亂碼問題,一般都是沒有正確調(diào)用setHeaderEncoding方法的原因。 
          1.2.3 FileItem類 
          FileItem類用來封裝單個表單字段元素的數(shù)據(jù),一個表單字段元素對應(yīng)一個FileItem對象,通過調(diào)用FileItem對象的方法可以獲得相關(guān)表單字 

          段元素的數(shù)據(jù)。FileItem是一個接口,在應(yīng)用程序中使用的實際上是該接口一個實現(xiàn)類,該實現(xiàn)類的名稱并不重要,程序可以采用FileItem接 

          口類型來對它進(jìn)行引用和訪問,為了便于講解,這里將FileItem實現(xiàn)類稱之為FileItem類。FileItem類還實現(xiàn)了Serializable接口,以支持序 

          列化操作。 
          對于“multipart/form-data”類型的FORM表單,瀏覽器上傳的實體內(nèi)容中的每個表單字段元素的數(shù)據(jù)之間用字段分隔界線進(jìn)行分割,兩個分隔 

          界線間的內(nèi)容稱為一個分區(qū),每個分區(qū)中的內(nèi)容可以被看作兩部分,一部分是對表單字段元素進(jìn)行描述的描述頭,另外一部是表單字段元素的 

          主體內(nèi)容,如圖1.3所示。 
          圖 1.3 
          主體部分有兩種可能性,要么是用戶填寫的表單內(nèi)容,要么是文件內(nèi)容。FileItem類對象實際上就是對圖1.3中的一個分區(qū)的數(shù)據(jù)進(jìn)行封裝的對 

          象,它內(nèi)部用了兩個成員變量來分別存儲描述頭和主體內(nèi)容,其中保存主體內(nèi)容的變量是一個輸出流類型的對象。當(dāng)主體內(nèi)容的大小小于 

          DiskFileUpload.setSizeThreshold方法設(shè)置的臨界值大小時,這個流對象關(guān)聯(lián)到一片內(nèi)存,主體內(nèi)容將會被保存在內(nèi)存中。當(dāng)主體內(nèi)容的數(shù)據(jù) 

          超過DiskFileUpload.setSizeThreshold方法設(shè)置的臨界值大小時,這個流對象關(guān)聯(lián)到硬盤上的一個臨時文件,主體內(nèi)容將被保存到該臨時文件 

          中。臨時文件的存儲目錄由DiskFileUpload.setRepositoryPath方法設(shè)置,臨時文件名的格式為“upload_00000005(八位或八位以上的數(shù)字) 

          .tmp”這種形式,F(xiàn)ileItem類內(nèi)部提供了維護(hù)臨時文件名中的數(shù)值不重復(fù)的機(jī)制,以保證了臨時文件名的唯一性。當(dāng)應(yīng)用程序?qū)⒅黧w內(nèi)容保存 

          到一個指定的文件中時,或者在FileItem對象被垃圾回收器回收時,或者Java虛擬機(jī)結(jié)束時,Apache文件上傳組件都會嘗試刪除臨時文件,以 

          盡量保證臨時文件能被及時清除。 
          下面介紹FileItem類中的幾個常用的方法: 
          1. isFormField方法 
          isFormField方法用于判斷FileItem類對象封裝的數(shù)據(jù)是否屬于一個普通表單字段,還是屬于一個文件表單字段,如果是普通表單字段則返回 

          true,否則返回false。該方法的完整語法定義如下: 
          public boolean isFormField() 
          2. getName方法 
          getName方法用于獲得文件上傳字段中的文件名,對于圖1.3中的第三個分區(qū)所示的描述頭,getName方法返回的結(jié)果為字符串“C:\bg.gif”。 

          如果FileItem類對象對應(yīng)的是普通表單字段,getName方法將返回null。即使用戶沒有通過網(wǎng)頁表單中的文件字段傳遞任何文件,但只要設(shè)置了 

          文件表單字段的name屬性,瀏覽器也會將文件字段的信息傳遞給服務(wù)器,只是文件名和文件內(nèi)容部分都為空,但這個表單字段仍然對應(yīng)一個 

          FileItem對象,此時,getName方法返回結(jié)果為空字符串"",讀者在調(diào)用Apache文件上傳組件時要注意考慮這個情況。getName方法的完整語法 

          定義如下: 
          public String getName() 
          注意:如果用戶使用Windows系統(tǒng)上傳文件,瀏覽器將傳遞該文件的完整路徑,如果用戶使用Linux或者Unix系統(tǒng)上傳文件,瀏覽器將只傳遞該 

          文件的名稱部分。 
          3.getFieldName方法 
          getFieldName方法用于返回表單字段元素的name屬性值,也就是返回圖1.3中的各個描述頭部分中的name屬性值,例如“name=p1”中的“p1” 

          。getFieldName方法的完整語法定義如下: 
          public String getFieldName() 
          4. write方法 
          write方法用于將FileItem對象中保存的主體內(nèi)容保存到某個指定的文件中。如果FileItem對象中的主體內(nèi)容是保存在某個臨時文件中,該方法 

          順利完成后,臨時文件有可能會被清除。該方法也可將普通表單字段內(nèi)容寫入到一個文件中,但它主要用途是將上傳的文件內(nèi)容保存在本地文 

          件系統(tǒng)中。其完整語法定義如下: 
          public void write(File file) 
          5.getString方法 
          getString方法用于將FileItem對象中保存的主體內(nèi)容作為一個字符串返回,它有兩個重載的定義形式: 
          public java.lang.String getString() 
          public java.lang.String getString(java.lang.String encoding) 
          throws java.io.UnsupportedEncodingException 
          前者使用缺省的字符集編碼將主體內(nèi)容轉(zhuǎn)換成字符串,后者使用參數(shù)指定的字符集編碼將主體內(nèi)容轉(zhuǎn)換成字符串。如果在讀取普通表單字段元 

          素的內(nèi)容時出現(xiàn)了中文亂碼現(xiàn)象,請調(diào)用第二個getString方法,并為之傳遞正確的字符集編碼名稱。 
          6. getContentType方法 
          getContentType 方法用于獲得上傳文件的類型,對于圖1.3中的第三個分區(qū)所示的描述頭,getContentType方法返回的結(jié)果為字符串 

          “image/gif”,即“Content-Type”字段的值部分。如果FileItem類對象對應(yīng)的是普通表單字段,該方法將返回null。getContentType 方法 

          的完整語法定義如下: 
          public String getContentType() 
          7. isInMemory方法 
          isInMemory方法用來判斷FileItem類對象封裝的主體內(nèi)容是存儲在內(nèi)存中,還是存儲在臨時文件中,如果存儲在內(nèi)存中則返回true,否則返回 

          false。其完整語法定義如下: 
          public boolean isInMemory() 
          8. delete方法 
          delete方法用來清空FileItem類對象中存放的主體內(nèi)容,如果主體內(nèi)容被保存在臨時文件中,delete方法將刪除該臨時文件。盡管Apache組件 

          使用了多種方式來盡量及時清理臨時文件,但系統(tǒng)出現(xiàn)異常時,仍有可能造成有的臨時文件被永久保存在了硬盤中。在有些情況下,可以調(diào)用 

          這個方法來及時刪除臨時文件。其完整語法定義如下: 
          public void delete() 
          1.2.4 FileUploadException類 
          在文件上傳過程中,可能發(fā)生各種各樣的異常,例如網(wǎng)絡(luò)中斷、數(shù)據(jù)丟失等等。為了對不同異常進(jìn)行合適的處理,Apache文件上傳組件還開發(fā) 

          了四個異常類,其中FileUploadException是其他異常類的父類,其他幾個類只是被間接調(diào)用的底層類,對于Apache組件調(diào)用人員來說,只需對 

          FileUploadException異常類進(jìn)行捕獲和處理即可。

          1.2.5 文件上傳編程實例 
          下面參考圖1.2中看到的示例代碼編寫一個使用Apache文件上傳組件來上傳文件的例子程序。 
          :動手體驗:使用Apache文件上傳組件 
          (1)在<tomcat安裝目錄>\webapps\fileupload目錄中按例程1-1編寫一個名為FileUpload.html的HTML頁面,該頁面用于提供文件上傳的FORM 

          表單,表單的enctype屬性設(shè)置值為“multipart/form-data”,表單的action屬性設(shè)置為“servlet/UploadServlet”。 
          例程1-1 FileUpload.html 



          <html> 
          <head> 
          <title>upload experiment</title> 
          <meta http-equiv="Content-Type" content="text/html; charset=gb2312"> 
          </head> 
          <body> 
          <h3>測試文件上傳組件的頁面</h3> 
          <form action="servlet/UploadServlet" 
          enctype="multipart/form-data" method="post"> 
          作者:<input type="text" name="author"><br> 
          來自:<input type="text" name="company"><br> 
          文件1:<input type="file" name="file1"><br> 
          文件2:<input type="file" name="file2"><br> 
          <input type="submit" value="上載"> 
          </form> 
          </body> 
          </html> 



          (2)在<tomcat的安裝目錄>\webapps\fileupload\src目錄中按例程1-2創(chuàng)建一個名為UploadServlet.java的Servlet程序,UploadServlet.java 

          調(diào)用Apache文件上傳組件來處理FORM表單提交的文件內(nèi)容和普通字段數(shù)據(jù)。 
          例程1-2 UploadServlet.java 



          import java.io.*; 
          import javax.servlet.*; 
          import javax.servlet.http.*; 
          import org.apache.commons.fileupload.*; 
          import java.util.*; 

          public class UploadServlet extends HttpServlet 

          public void doPost(HttpServletRequest request, 
          HttpServletResponse response) throws ServletException,IOException 

          response.setContentType("text/html;charset=gb2312"); 
          PrintWriter out = response.getWriter(); 

          //設(shè)置保存上傳文件的目錄 
          String uploadDir = getServletContext().getRealPath("/upload"); 
          if (uploadDir == null) 

          out.println("無法訪問存儲目錄!"); 
          return; 

          File fUploadDir = new File(uploadDir); 
          if(!fUploadDir.exists()) 

          if(!fUploadDir.mkdir()) 

          out.println("無法創(chuàng)建存儲目錄!"); 
          return; 



          if (!DiskFileUpload.isMultipartContent(request)) 

          out.println("只能處理multipart/form-data類型的數(shù)據(jù)!"); 
          return ; 


          DiskFileUpload fu = new DiskFileUpload(); 
          //最多上傳200M數(shù)據(jù) 
          fu.setSizeMax(1024 * 1024 * 200); 
          //超過1M的字段數(shù)據(jù)采用臨時文件緩存 
          fu.setSizeThreshold(1024 * 1024); 
          //采用默認(rèn)的臨時文件存儲位置 
          //fu.setRepositoryPath(...); 
          //設(shè)置上傳的普通字段的名稱和文件字段的文件名所采用的字符集編碼 
          fu.setHeaderEncoding("gb2312"); 

          //得到所有表單字段對象的集合 
          List fileItems = null; 
          try 

          fileItems = fu.parseRequest(request); 

          catch (FileUploadException e) 

          out.println("解析數(shù)據(jù)時出現(xiàn)如下問題:"); 
          e.printStackTrace(out); 
          return; 


          //處理每個表單字段 
          Iterator i = fileItems.iterator(); 
          while (i.hasNext()) 

          FileItem fi = (FileItem) i.next(); 
          if (fi.isFormField()) 

          String content = fi.getString("GB2312"); 
          String fieldName = fi.getFieldName(); 
          request.setAttribute(fieldName,content); 

          else 

          try 

          String pathSrc = fi.getName(); 
          /*如果用戶沒有在FORM表單的文件字段中選擇任何文件, 
          那么忽略對該字段項的處理*/ 
          if(pathSrc.trim().equals("")) 

          continue; 

          int start = pathSrc.lastIndexOf('\\'); 
          String fileName = pathSrc.substring(start + 1); 
          File pathDest = new File(uploadDir, fileName); 

          fi.write(pathDest); 
          String fieldName = fi.getFieldName(); 
          request.setAttribute(fieldName, fileName); 

          catch (Exception e) 

          out.println("存儲文件時出現(xiàn)如下問題:"); 
          e.printStackTrace(out); 
          return; 

          finally //總是立即刪除保存表單字段內(nèi)容的臨時文件 

          fi.delete(); 





          //顯示處理結(jié)果 
          out.println("用戶:" + request.getAttribute("author") + "<br>"); 
          out.println("來自:" + request.getAttribute("company") + "<br>"); 

          /*將上傳的文件名組合成"file1,file2"這種形式顯示出來,如果沒有上傳 
          *任何文件,則顯示為"無",如果只上傳了第二個文件,顯示為"file2"。*/ 
          StringBuffer filelist = new StringBuffer(); 
          String file1 = (String)request.getAttribute("file1"); 
          makeUpList(filelist,file1); 
          String file2 = (String)request.getAttribute("file2"); 
          makeUpList(filelist,file2); 
          out.println("成功上傳的文件:" + 
          (filelist.length()==0 ? "無" : filelist.toString())); 



          /** 
          *將一段字符串追加到一個結(jié)果字符串中。如果結(jié)果字符串的初始內(nèi)容不為空, 
          *在追加當(dāng)前這段字符串之前先最加一個逗號(,)。在組合sql語句的查詢條件時, 
          *經(jīng)常要用到類似的方法,第一條件前沒有"and",而后面的條件前都需要用"and" 
          *作連詞,如果沒有選擇第一個條件,第二個條件就變成第一個,依此類推。 

          *@param result 要將當(dāng)前字符串追加進(jìn)去的結(jié)果字符串 
          *@param fragment 當(dāng)前要追加的字符串 
          */ 
          private void makeUpList(StringBuffer result,String fragment) 

          if(fragment != null) 

          if(result.length() != 0) 

          result.append(","); 

          result.append(fragment); 






          在Windows資源管理器窗口中將UploadServlet.java源文件拖動到compile.bat文件的快捷方式上進(jìn)行編譯,修改Javac編譯程序報告的錯誤,直 

          到編譯成功通過為止。 
          (3)修改<tomcat的安裝目錄>\webapps\fileupload\WEB-INF\classes\web.xml文件,在其中注冊和映射UploadServlet的訪問路徑,如例程1-3 

          所示。 
          例程1-3 web.xml 



          <web-app> 
          <servlet> 
          <servlet-name>UploadServlet</servlet-name> 
          <servlet-class>UploadServlet</servlet-class> 
          </servlet> 

          <servlet-mapping> 
          <servlet-name>UploadServlet</servlet-name> 
          <url-pattern>/servlet/UploadServlet</url-pattern> 
          </servlet-mapping> 
          </web-app> 
          (4)重新啟動Tomcat,并在瀏覽器地址欄中輸入如下地址: 
          http://localhost:8080/fileupload/FileUpload.html 
          填寫返回頁面中的FORM表單,如圖1.4所示,單擊“上載”按鈕后,瀏覽器返回的頁面信息如圖1.5所示。 
          圖1.4 
          圖1.5(這些圖的標(biāo)題欄中的it315改為fileupload) 
          查看<tomcat安裝目錄>\webapps\it315\upload目錄,可以看到剛才上傳的兩個文件。 
          (4)單擊瀏覽器工具欄上的“后退”按鈕回到表單填寫頁面,只在第二個文件字段中選擇一個文件,單擊“上載”按鈕,瀏覽器返回的顯示結(jié)果 

          如圖1.6所示。 
          圖1.6 
          M腳下留心: 
          上面編寫的Servlet程序?qū)⑸蟼鞯奈募4嬖诹水?dāng)前WEB應(yīng)用程序下面的upload目錄中,這個目錄是客戶端瀏覽器可以訪問到的目錄。如果用戶 

          通過瀏覽器上傳了一個名稱為test.jsp的文件,那么用戶接著就可以在瀏覽器中訪問這個test.jsp文件了,對于本地瀏覽器來說,這個jsp文件 

          的訪問URL地址如下所示: 
          http://localhost:8080/fileupload/upload/test.jsp 
          對于遠(yuǎn)程客戶端瀏覽器而言,只需要將上面的url地址中的localhost改寫為Tomcat服務(wù)器的主機(jī)名或IP地址即可。用戶可以通過上面的Servlet 

          程序來上傳自己編寫的jsp文件,然后又可以通過瀏覽器來訪問這個jsp文件,如果用戶在jsp文件中編寫一些有害的程序代碼,例如,查看服務(wù) 

          器上的所有目錄結(jié)構(gòu),調(diào)用服務(wù)器上的操作系統(tǒng)進(jìn)程等等,這將是一個非常致命的安全漏洞和隱患,這臺服務(wù)器對外就沒有任何安全性可言了 

          。 

          1.3 Apache文件上傳組件的源碼賞析 
          經(jīng)常閱讀一些知名的開源項目的源代碼,可以幫助我們開闊眼界和快速提高編程能力。Apache文件上傳組件是Apache組織開發(fā)的一個開源項目 

          ,從網(wǎng)址http://jakarta.apache.org/commons/fileupload可以下載到Apache組件的源程序包,在本書的附帶帶光盤中也提供了該組件的源程 

          序包,文件名為commons-fileupload-1.0-src.zip。該組件的設(shè)計思想和程序編碼細(xì)節(jié)包含有許多值得借鑒的技巧,為了便于有興趣的讀者學(xué) 

          習(xí)和研究該組件的源碼,本節(jié)將分析Apache文件上傳組件的源代碼實現(xiàn)。對于只想了解如何使用Apache文件上傳組件來上傳文件的讀者來說, 

          不必學(xué)習(xí)本節(jié)的內(nèi)容。在學(xué)習(xí)本節(jié)內(nèi)容之前,讀者需要仔細(xì)學(xué)習(xí)了筆者編著的《深入體驗java Web開發(fā)內(nèi)幕——核心基礎(chǔ)》一書中的第6.7.2節(jié) 

          中講解的“分析文件上傳的請求消息結(jié)構(gòu)”的知識。 
          1.3.1 Apache文件上傳組的類工作關(guān)系 
          Apache文件上傳組件總共由兩個接口,十二個類組成。在Apache文件上傳組件的十二個類中,有兩個抽象類,四個的異常類,六個主要類,其 

          中FileUpLoad類用暫時沒有應(yīng)用,是為了以后擴(kuò)展而保留的。Apache文件上傳組件中的各個類的關(guān)系如圖1.7所示,圖中省略了異常類。 
          圖 1.7 

          DiskFileUpload類是文件上傳組件的核心類,它是一個總的控制類,首先由Apache文件上傳組件的使用者直接調(diào)用DiskFileUpload類的方法, 

          DiskFileUpload類再調(diào)用和協(xié)調(diào)更底層的類來完成具體的功能。解析類MultipartStream和工廠類DefaultFileItemFactory就是DiskFileUpload 

          類調(diào)用的兩個的底層類。MultipartStream類用于對請求消息中的實體數(shù)據(jù)進(jìn)行具體解析,DefaultFileItemFactory類對MultipartStream類解 

          析出來的數(shù)據(jù)進(jìn)行封裝,它將每個表單字段數(shù)據(jù)封裝成一個個的FileItem類對象,用戶通過FileItem類對象來獲得相關(guān)表單字段的數(shù)據(jù)。 
          DefaultFileItem是FileItem接口的實現(xiàn)類,實現(xiàn)了FileItem接口中定義的功能,用戶只需關(guān)心FileItem接口,通過FileItem接口來使用 

          DefaultFileItem類實現(xiàn)的功能。DefaultFileItem類使用了兩個成員變量來分別存儲表單字段數(shù)據(jù)的描述頭和主體內(nèi)容,其中保存主體內(nèi)容的 

          變量類型為DeferredFileOutputStream類。DeferredFileOutputStream類是一個輸出流類型,在開始時,DeferredFileOutputStream類內(nèi)部使 

          用一個ByteArrayOutputStream類對象來存儲數(shù)據(jù),當(dāng)寫入它里面的主體內(nèi)容的大小大于DiskFileUpload.setSizeThreshold方法設(shè)置的臨界值 

          時,DeferredFileOutputStream類內(nèi)部創(chuàng)建一個文件輸出流對象來存儲數(shù)據(jù),并將前面寫入到ByteArrayOutputStream類對象中的數(shù)據(jù)轉(zhuǎn)移到文 

          件輸出流對象中。這個文件輸出流對象關(guān)聯(lián)的文件是一個臨時文件,它的保存路徑由DiskFileUpload.setRepositoryPath方法指定。 
          Apache文件上傳組件的處理流程如圖1.8所示。 
          圖1.8 
          圖1.8中的每一步驟的詳細(xì)解釋如下: 
          (1)Web容器接收用戶的HTTP請求消息,創(chuàng)建request請求對象。 
          (2)調(diào)用DiskFileUpload類對象的parseRequest方法對request請求對象進(jìn)行解析。該方法首先檢查request請求對象中的數(shù)據(jù)內(nèi)容是否是 

          “multipart/form-data”類型,如果是,該方法則創(chuàng)建MultipartStream類對象對request請求對象中的請求體 進(jìn)行解析。 
          (3)MultipartStream類對象對request請求體進(jìn)行解析,并返回解析出的各個表單字段元素對應(yīng)的內(nèi)容。 
          (4)DiskFileUpload類對象的parseRequest方法接著創(chuàng)建DefaultFileItemFactory類對象,用來將MultipartStream類對象解析出的每個表單 

          字段元素的數(shù)據(jù)封裝成FileItem類對象。 
          (5)DefaultFileItemFactory工廠類對象把MultipartStream類對象解析出的各個表單字段元素的數(shù)據(jù)封裝成若干DefaultFileItem類對象,然 

          后加入到一個List類型的集合對象中,parseRequest方法返回該List集合對象。 
          實際上,步驟(3)和步驟(5)是交替同步進(jìn)行的,即在MultipartStream類對象解析每個表單字段元素時,都會調(diào)用DefaultFileItemFactory 

          工廠類把該表單字段元素封裝成對應(yīng)的FileItem類對象。 

          1.3.2 Apache文件上傳組件的核心編程問題 
          WEB服務(wù)器端程序接收到“multipart/form-data”類型的HTTP請求消息后,其核心和基本的編程工作就是讀取請求消息中的實體內(nèi)容,然后解 

          析出每個分區(qū)的數(shù)據(jù),接著再從每個分區(qū)中解析出描述頭和主體內(nèi)容部分。 
          在讀取HTTP請求消息中的實體內(nèi)容時,只能調(diào)用HttpServletRequest.getInputStream方法返回的字節(jié)輸入流,而不能調(diào)用 

          HttpServletRequest.getReader方法返回的字符輸入流,因為不管上傳的文件類型是文本的、還是其他各種格式的二進(jìn)制內(nèi)容,WEB服務(wù)器程序 

          要做的工作就是將屬于文件內(nèi)容的那部分?jǐn)?shù)據(jù)原封不動地提取出來,然后原封不動地存儲到本地文件系統(tǒng)中。如果使用 

          HttpServletRequest.getReader方法返回的字符輸入流對象來讀取HTTP請求消息中的實體內(nèi)容,它將HTTP請求消息中的字節(jié)數(shù)據(jù)轉(zhuǎn)換成字符后 

          再返回,這主要是為了方便要以文本方式來處理本來就全是文本內(nèi)容的請求消息的應(yīng)用,但本程序要求的是“原封不動”,顯然不能使用 

          HttpServletRequest.getReader方法返回的字符輸入流對象來進(jìn)行讀取。 
          另外,不能期望用一個很大的字節(jié)數(shù)組就可以裝進(jìn)HTTP請求消息中的所有實體內(nèi)容,因為程序中定義的字節(jié)數(shù)組大小總是有限制的,但應(yīng)該允 

          許客戶端上傳超過這個字節(jié)數(shù)組大小的實體內(nèi)容。所以,只能創(chuàng)建一個一般大小的字節(jié)數(shù)組緩沖區(qū)來逐段讀取請求消息中的實體內(nèi)容,讀取一 

          段就處理一段,處理完上一段以后,再讀取下一段,如此循環(huán),直到處理完所有的實體內(nèi)容,如圖1.9所示。 
          圖 1.9 
          在圖1.9中,buffer即為用來逐段讀取請求消息中的實體內(nèi)容的字節(jié)數(shù)組緩沖區(qū)。因為讀取到緩沖區(qū)中的數(shù)據(jù)處理完后就會被拋棄,確切地說, 

          是被下一段數(shù)據(jù)覆蓋,所以,解析和封裝過程必須同步進(jìn)行,程序一旦識別出圖1.3中的一個分區(qū)的開始后,就要開始將它封裝到一個FileItem 

          對象中。 
          程序要識別出圖1.3中的每一個分區(qū),需要在圖1.9所示的字節(jié)數(shù)組緩沖區(qū)buffer中尋找分區(qū)的字段分隔界線,當(dāng)找到一個字段分隔界線后,就 

          等于找到了一個分區(qū)的開始。筆者在《深入體驗java Web開發(fā)內(nèi)幕——核心基礎(chǔ)》一書中的第6.7.2節(jié)中已經(jīng)講過,上傳文件的請求消息的 

          Content-Type頭字段中包含有用作字段分隔界線的字符序列,如下所示: 
          content-type : multipart/form-data; boundary=---------------------------7d51383203e8 
          顯然,我們可以通過調(diào)用HttpServletRequest.getHeader方法讀取Content-Type頭字段的內(nèi)容,從中分離出分隔界線的字符序列,然后在字節(jié) 

          數(shù)組緩沖區(qū)buffer中尋找分區(qū)的字段分隔界線。content-type頭字段的boundary參數(shù)中指定的字段分隔界線是瀏覽器隨機(jī)產(chǎn)生的,瀏覽器保證 

          它不會與用戶上傳的所有數(shù)據(jù)中的任何部分出現(xiàn)相同。在這里有一點需要注意,圖1.3中的實體內(nèi)容內(nèi)部的字段分隔界線與content-type頭中指 

          定的字段分隔界線有一點細(xì)微的差別,前者是在后者前面增加了兩個減號(-)字符而形成的,這倒不是什么編程難點。真正的編程難點在于在 

          字節(jié)數(shù)組緩沖區(qū)buffer中尋找分隔界線時,可能會遇到字節(jié)數(shù)組緩沖區(qū)buffer中只裝入了分隔界線字符序列的部分內(nèi)容的情況,如圖1.10所示 

          。 
          圖1.10 

          要解決這個問題的方法之一就是在查找字段分隔界線時,如果發(fā)現(xiàn)字節(jié)數(shù)組緩沖區(qū)buffer中只裝入了分隔界線字符序列的部分內(nèi)容,那么就將 

          這一部分內(nèi)容留給字節(jié)數(shù)組緩沖區(qū)buffer的下一次讀取,如圖1.11所示。 
          圖1.11 
          這種方式讓字節(jié)數(shù)組緩沖區(qū)buffer下一次讀取的內(nèi)容不是緊接著上一次讀取內(nèi)容的后面,而是重疊上一次讀取的一部分內(nèi)容,即從上一次讀取 

          內(nèi)容中的分隔界線字符序列的第一個字節(jié)處開始讀取。這種方式在實際的編程處理上存在著相當(dāng)大的難度,程序首先必須確定字節(jié)數(shù)組緩沖區(qū) 

          buffer上一次讀取的數(shù)據(jù)的后一部分內(nèi)容正好是分隔界線字符序列的前面一部分內(nèi)容,而這一部分內(nèi)容的長度是不確定的,可能只是分隔界線 

          字符序列的第一個字符,也可能是分隔界線字符序列的前面n-1個字符,其中n為分隔界線字符序列的整個長度。另外,即使確定字節(jié)數(shù)組緩沖 

          區(qū)buffer上一次讀取的數(shù)據(jù)的后一部分內(nèi)容正好是分隔界線字符序列的前面一部分內(nèi)容,但它們在整個輸入字節(jié)流中的后續(xù)內(nèi)容不一定就整個 

          分隔界線字符序列的后一部分內(nèi)容,出現(xiàn)這種情況的可能性是完全存在,程序必須進(jìn)行全面和嚴(yán)謹(jǐn)?shù)目紤]。 
          Apache文件上傳組件的解決方法比較巧妙,它在查找字段分隔界線時,如果搜索到最后第n個字符時,n為分隔界線字符序列的長度,發(fā)現(xiàn)最后n 

          個字符不能與分隔界線字符序列匹配,則將最后的n-1個字符留給字節(jié)數(shù)組緩沖區(qū)buffer的下一次讀取,程序再對buffer的下一次讀取的整個內(nèi) 

          容從頭開始查找字段分隔界線,如圖1.12所示。 
          圖1.12 

          Apache文件上傳組件查找字段分隔界線的具體方法,讀者可以請參見MultipartStream類的findSeparator()方法中的源代碼。 
          當(dāng)找到一個分區(qū)的開始位置后,程序還需要分辨出分區(qū)中的描述頭和主體內(nèi)容,并對這兩部分內(nèi)容分開存儲。如何分辨出一個分區(qū)的描述頭和 

          主體部分呢?從圖1.3中可以看到,每個分區(qū)中的描述頭和主體內(nèi)容之間有一空行,再加上描述頭后面的換行,這就說明描述頭和主體部分之間 

          是使用“\n”、“\r”、“\n”、“\r”這四個連續(xù)的字節(jié)內(nèi)容進(jìn)行分隔。因此,程序需要把“\n”、“\r”、“\n”、“\r”這四個連續(xù)的 

          字節(jié)內(nèi)容作為描述頭和主體部分之間的分隔界線,并在字節(jié)數(shù)組緩沖區(qū)buffer中尋找這個特殊的分隔界線來識別描述頭和主體部分。 
          當(dāng)識別出一個分區(qū)中的描述頭和主體部分后,程序需要解決的下一個問題就是如何將描述頭和主體部分的數(shù)據(jù)保存到FileItem對象中,以便用 

          戶以后可以調(diào)用FileItem類的方法來獲得這些數(shù)據(jù)。主體部分的數(shù)據(jù)需要能夠根據(jù)用戶上傳的文件大小有伸縮性地進(jìn)行存儲,因此,程序要求 

          編寫一個特殊的類來封裝主體部分的數(shù)據(jù),對于這個問題的具體實現(xiàn)細(xì)節(jié),讀者可參見1.2.4小節(jié)中講解的DeferredFileOutputStream類來了解 

          1.3.3 MultipartStream類 
          MultipartStream類用來對上傳的請求輸入流進(jìn)行解析,它是整個Apache上傳組件中最復(fù)雜的類。 
          1.設(shè)計思想 
          MultipartStream類中定義了一個byte[]類型的boundary成員變量,這個成員變量用于保存圖1.3中的各個數(shù)據(jù)分區(qū)之間的分隔界線,每個分區(qū) 

          分別代表一個表單字段的信息。圖1.3中的每個分區(qū)又可以分為描述頭部分和主體部分,MultipartStream類中定義了一個readHeaders()方法來 

          讀取描述頭部分的內(nèi)容,MultipartStream類中定義了一個readBodyData(OutputStream output)方法來讀取主體部分的內(nèi)容,并將這些內(nèi)容寫 

          入到一個作為參數(shù)傳入進(jìn)來的輸出流對象中。readBodyData方法接收的參數(shù)output對象在應(yīng)用中的實際類型是DeferredFileOutputStream,這 

          個對象又是保存在DefaultFileItem類對象中的一個成員變量,這樣,readBodyData方法就可以將一個分區(qū)的主體部分的數(shù)據(jù)寫入到 

          DefaultFileItem類對象中。 
          因為圖1.3中的實體內(nèi)容內(nèi)部的字段分隔界線是在content-type頭中指定的字段分隔界線前面增加了兩個減號(-)字符而形成的,而每個字段 

          分隔界線與它前面內(nèi)容之間還進(jìn)行了換行,這個換行并不屬于表單字段元素的內(nèi)容。所以,MultipartStream類中的成員變量boundary中存儲的 

          字節(jié)數(shù)組并不是直接從content-type頭的boundary參數(shù)中獲得的字符序列,而是在boundary參數(shù)中指定的字符序列前面增加了四個字節(jié),依次 

          是‘\n’、‘\r’、‘-’和‘-’。MultipartStream類中定義了一個readBoundary()方法來讀取和識別各個字段之間分隔界線,有一點特殊的 

          是,圖1.3中的第一個分隔界線前面沒有回車換行符,它是無法與成員變量boundary中的數(shù)據(jù)相匹配的,所以無法調(diào)用readBoundary()方法進(jìn)行 

          讀取,而是需要進(jìn)行特殊處理,其后的每個分隔界線都與boundary中的數(shù)據(jù)相匹配,可以直接調(diào)用readBoundary()方法進(jìn)行讀取。在本章的后 

          面部分,如果沒有特別說明,所說的分隔界線都是指成員變量boundary中的數(shù)據(jù)內(nèi)容。 
          RFC 1867格式規(guī)范規(guī)定了描述頭和主體部分必須用一個空行進(jìn)行分隔,如圖1.3所示,也就是描述頭和主體部分使用“\n”、“\r”、“\n”、 

          “\r”這四個連續(xù)的字節(jié)內(nèi)容進(jìn)行分隔。MultipartStream類的設(shè)計者為了簡化編程,在readHeaders()方法中將“\n”、“\r”、“\n”、 

          “\r”這四個連續(xù)的字節(jié)內(nèi)容連同描述頭一起進(jìn)行讀取。readHeaders()方法在讀取數(shù)據(jù)的過程中,當(dāng)它發(fā)現(xiàn)第一個‘\n’、‘\r’、‘\n’、 

          ‘\r’ 連續(xù)的字節(jié)序列時就會返回,即使主體部分正好也包含了“\n”、“\r”、“\n”、“\r”這四個連續(xù)的字節(jié)內(nèi)容,但是,它們只會被 

          隨后調(diào)用的readBodyData方法作為主體內(nèi)容讀取,永遠(yuǎn)不會被readHeaders()方法讀取到,所以,它們不會與作為描述頭和主體部分的分隔字符 

          序列發(fā)生沖突。 
          由于readHeaders()方法讀取了一個分區(qū)中的主體部分前面的所有內(nèi)容(包括它前面的換行),而它與下一個分區(qū)之間的分隔界線前面的換行又 

          包含在了成員變量boundary中,這個換行將被readBoundary()方法讀取,所以,夾在readheaders()方法讀取的內(nèi)容和readBoundary()方法讀取 

          的內(nèi)容之間的數(shù)據(jù)全部都屬于表單字段元素的內(nèi)容了,因此,讀取分區(qū)中的主體部分的readBodyData(OutputStream output)方法不需要進(jìn)行特 

          別的處理,它直接將讀取的數(shù)據(jù)寫入到DefaultFileItem類對象中封裝的DeferredFileOutputStream屬性對象中即可。 
          2. 構(gòu)造方法 
          MultipartStream類中的一個主要的構(gòu)造方法的語法定義如下: 
          public (InputStream input, byte[] boundary, int bufSize) 
          其中,參數(shù)input是指從HttpServetRequest請求對象中獲得的字節(jié)輸入流對象,參數(shù)boundary是從請求消息頭中獲得的未經(jīng)處理的分隔界線, 

          bufSize指定圖1.10中的buffer緩沖區(qū)字節(jié)數(shù)組的長度,默認(rèn)值是4096個字節(jié)。這個構(gòu)造方法的源代碼如下: 
          public MultipartStream(InputStream input, byte[] boundary, int bufSize) 

          // 初始化成員變量 
          this.input = input; 
          this.bufSize = bufSize; 
          this.buffer = new byte[bufSize]; 
          this.boundary = new byte[boundary.length + 4]; 
          this.boundaryLength = boundary.length + 4; 
          //buffer緩沖區(qū)中保留給下次讀取的最大字節(jié)個數(shù) 
          this.keepRegion = boundary.length + 3; 
          this.boundary[0] = 0x0D; //‘\n’的16進(jìn)制形式 
          this.boundary[1] = 0x0A; //‘\r’的16進(jìn)制形式 
          this.boundary[2] = 0x2D; //‘-’的16進(jìn)制形式 
          this.boundary[3] = 0x2D; 
          //在成員變量boundary中生成最終的分隔界線 
          System.arraycopy (boundary, 0, this.boundary, 4, boundary.length); 

          head = 0; // 成員變量,表示正在處理的這個字節(jié)在buffer中的位置指針 
          tail = 0; // 成員變量,表示實際讀入到buffer中的字節(jié)個數(shù) 


          3. readByte方法 
          MultipartStream類中的readByte()方法從字節(jié)數(shù)組緩沖區(qū)buffer中讀一個字節(jié),當(dāng)buffer緩沖區(qū)中沒有更多的數(shù)據(jù)可讀時,該方法會自動從輸 

          入流中讀取一批新的字節(jié)數(shù)據(jù)來重新填充buffer緩沖區(qū)。readByte()方法的源代碼如下: 
          public byte readByte () throws IOException 

          // 判斷是否已經(jīng)讀完了buffer緩沖區(qū)中的所有數(shù)據(jù) 
          if (head == tail) 

          head = 0; 
          //讀入新的數(shù)據(jù)內(nèi)容來填充buffer緩沖區(qū) 
          tail = input.read(buffer, head, bufSize); 
          if (tail == -1) 

          throw new IOException("No more data is available "); 


          return buffer[head++];// 返回當(dāng)前字節(jié),head++ 

          其中,head變量是MultipartStream類中定義的一個int類型的成員變量,它用于表示正在讀取的字節(jié)在buffer數(shù)組緩沖區(qū)中的位置;tail變量 

          也是MultipartStream類中定義的一個int類型的成員變量,它用于表示當(dāng)前buffer數(shù)組緩沖區(qū)裝入的實際字節(jié)內(nèi)容的長度。在MultipartStream 

          類中主要是通過控制成員變量head的值來控制對buffer緩沖區(qū)中的數(shù)據(jù)的讀取和直接跳過某段數(shù)據(jù),通過比較head與tail變量的值了解是否需 

          要向buffer緩沖區(qū)中裝入新的數(shù)據(jù)內(nèi)容。當(dāng)每次向buffer緩沖區(qū)中裝入新的數(shù)據(jù)內(nèi)容后,都應(yīng)該調(diào)整成員變量head和tail的值。 

          4. arrayequals靜態(tài)方法 
          MultipartStream類中定義了一個的arrayequals靜態(tài)方法,用于比較兩個字節(jié)數(shù)組中的前面一部分內(nèi)容是否相等,相等返回true,否則返回 

          false。arrayequals方法的源代碼如下,參數(shù)count指定了對字節(jié)數(shù)組中的前面幾個字節(jié)內(nèi)容進(jìn)行比較: 
          public static boolean arrayequals(byte[] a, byte[] b,int count) 

          for (int i = 0; i < count; i++) 

          if (a[i] != b[i]) 

          return false; 


          return true; 


          5. findByte方法 
          MultipartStream類中的findByte()方法從字節(jié)數(shù)組緩沖區(qū)buffer中的某個位置開始搜索一個特定的字節(jié)數(shù)據(jù),如果找到了,則返回該字節(jié)在 

          buffer緩沖區(qū)中的位置,不再繼續(xù)搜索,如果沒有找到,則返回-1。findByte方法的源代碼如下,參數(shù)pos制定了不搜索的起始位置值,value 

          是要搜索的字節(jié)數(shù)據(jù): 
          protected int findByte(byte value,int pos) 

          for (int i = pos; i < tail; i++) 

          if (buffer[i] == value) 

          return i; // 找到該值,findByte方法返回 


          return - 1; 

          如果程序需要在buffer緩沖區(qū)中多次搜索某個特定的字節(jié)數(shù)據(jù),那就可以循環(huán)調(diào)用findByte方法,只是在每次調(diào)用findByte方法時,必須不斷 

          地改變參數(shù)pos的值,讓pos的值等于上次調(diào)用findByte的返回值,直到findByte方法返回-1時為止,如圖1.13所示。 
          圖1.13 
          6. findSeparator方法 
          MultipartStream類中的findSeparator方法用于從字節(jié)數(shù)組緩沖區(qū)buffer中查找成員變量boundary中定義的分隔界線,并返回分隔界線的第一 

          個字節(jié)在buffer緩沖區(qū)中的位置,如果在buffer緩沖區(qū)中沒有找到分隔界線,則返回-1。 
          findSeparator方法內(nèi)部首先調(diào)用findByte方法在buffer緩沖區(qū)中搜索分隔界線boundary的第一個字節(jié)內(nèi)容,如果沒有找到,則說明buffer緩沖 

          區(qū)中沒有包含分隔界線;如果findByte方法在buffer緩沖區(qū)中找到了分隔界線boundary的第一個字節(jié)內(nèi)容,findSeparator方法內(nèi)部接著確定該 

          字節(jié)及隨后的字節(jié)序列是否確實是分隔界線。findSeparator方法內(nèi)部循環(huán)調(diào)用findByte方法,直到找到分隔界線或者findByte方法已經(jīng)查找到 

          了buffer緩沖區(qū)中的最后boundaryLength -1個字節(jié)。findSeparator方法內(nèi)部為什么調(diào)用findByte方法查找到buffer緩沖區(qū)中的最后 

          boundaryLength-1個字節(jié)時就停止查找呢?這是為了解決如圖1.10所示的buffer緩沖區(qū)中裝入了分隔界線的部分內(nèi)容的特殊情況,所以在 

          findSeparator()方法中不要搜索buffer緩沖區(qū)中的最后的boundaryLength -1個字節(jié),而是把buffer緩沖區(qū)中的最后這boundaryLength -1個字 

          節(jié)作為保留區(qū),在下次讀取buffer緩沖區(qū)時將這些保留的字節(jié)數(shù)據(jù)重新填充到buffer緩沖區(qū)的開始部分。findSeparator方法的源代碼如下: 
          protected int findSeparator() 

          int first; 
          int match = 0; 
          int maxpos = tail - boundaryLength;//在buffer中搜索的最大位置 

          for (first = head;(first <= maxpos) && (match != boundaryLength); 
          first++) 

          //在buffer緩沖區(qū)中尋找boundary的第一個字節(jié) 
          first = findByte(boundary[0], first); 
          /*buffer中找不到boundary[0]或者boundary[0]位于保留區(qū)中, 
          則可以判斷buffer中不存在分隔界線*/ 
          if (first == -1 || (first > maxpos)) 

          return -1; 

          //確定隨后的字節(jié)序列是否確實是分隔界線的其他字節(jié)內(nèi)容 
          for (match = 1; match < boundaryLength; match++) 

          if (buffer[first + match] != boundary[match]) 

          break; 



          // 當(dāng)前buffer中找到boundary,返回第一個字節(jié)所在位置值 
          if (match == boundaryLength) 

          return first - 1; 

          return -1; // 當(dāng)前buffer中沒找到boundary,返回-1 

          圖1.14中描述了findSeparator方法內(nèi)部定義的各個變量的示意圖。 
          圖1.14 
          findSeparator方法內(nèi)部的代碼主要包括如下三個步驟: 
          (1)循環(huán)調(diào)用findByte(boundary[0], first)找到buffer緩沖區(qū)中的與boundary[0]相同的字節(jié)的位置,并將位置記錄在first變量中。 
          (2)比較buffer緩沖區(qū)中的first后的boundaryLength-1個字節(jié)序列是否與boundary中的其他字節(jié)序列相同。如果不同,說明這個first變量指 

          向的字節(jié)不是分隔界線的開始字節(jié),跳出內(nèi)循環(huán),將first變量加1后繼續(xù)外循環(huán)調(diào)用findByte方法;如果相同,說明在當(dāng)前緩沖區(qū)buffer中找 

          到了分隔界線,內(nèi)循環(huán)正常結(jié)束,此時match變量的值為boundaryLength,接著執(zhí)行外循環(huán)將first變量加1,然后執(zhí)行外循環(huán)的條件判斷,由于 

          match != boundaryLength條件不成立,外循環(huán)也隨之結(jié)束。 
          (3)判斷match是否等于boundaryLength,如果等于則說明找到了分隔界線,此時返回成員變量boundary的第一個字節(jié)在緩沖區(qū)buffer中位置 

          ,由于第(2)中將first加1了,所以這里的返回值應(yīng)該是first-1;如果不等,說明當(dāng)前緩沖區(qū)huffer中沒有分隔界線,返回-1。 

          7. readHeaders方法 
          MultipartStream類中的readHeaders方法用于讀取一個分區(qū)的描述頭部分,并根據(jù)DiskFileUpload類的setHeaderEncoding方法設(shè)定的字符集編 

          碼將描述頭部分轉(zhuǎn)換成一個字符串返回。 
          在調(diào)用readHeaders方法之前時,程序已經(jīng)調(diào)用了findSeparator方法找到了分隔界線和讀取了分隔界線前面的內(nèi)容,此時MultipartStream類中 

          的成員變量head指向了buffer緩沖區(qū)中的分隔界線boundary的第一個字節(jié),程序接著應(yīng)調(diào)用readBoundary方法跳過分隔界線及其隨后的回車換 

          行兩個字節(jié),以保證在調(diào)用readHeaders方法時,成員變量head已經(jīng)指向了分區(qū)的描述頭的第一個字節(jié)。在readHeaders方法內(nèi)部,直接循環(huán)調(diào) 

          用readByte方法讀取字節(jié)數(shù)據(jù),并把讀到的數(shù)據(jù)存儲在一個字節(jié)數(shù)組輸出流中,直到讀取到了連續(xù)的兩次回車換行字符,就認(rèn)為已經(jīng)讀取完了 

          描述頭的全部內(nèi)容,此時成員變量head將指向分區(qū)中的主體內(nèi)容的第一個字節(jié)。readHeaders()方法的源代碼如下: 
          public String readHeaders()throws MalformedStreamException 

          int i = 0; 
          //從下面的代碼看來,這里定義成一個byte即可,不用定義成byte數(shù)組 
          byte b[] = new byte[1]; 
          //用于臨時保存描述頭信息的字節(jié)數(shù)組輸出流 
          ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
          //對描述頭部分的數(shù)據(jù)內(nèi)容過大進(jìn)行限制處理 
          int sizeMax = HEADER_PART_SIZE_MAX; 
          int size = 0; 
          while (i < 4) 

          try 

          b[0] = readByte(); } 
          catch (IOException e) 

          throw new MalformedStreamException("Stream ended unexpectedly " ); 

          size++; 
          //靜態(tài)常量HEADER_SEPARATOR的值為:{0x0D, 0x0A, 0x0D, 0x0A} 
          if (b[0] == HEADER_SEPARATOR[i]) 

          i++; 

          else 

          i = 0; 

          if (size <= sizeMax) 

          baos.write(b[0]); // 將當(dāng)前字節(jié)存入緩沖流 


          String headers = null; // 找到HEADER_SEPARATOR后,獲取描述頭 
          if (headerEncoding != null) 

          try 

          headers = baos.toString(headerEncoding); 

          catch (UnsupportedEncodingException e) 

          headers = baos.toString(); 


          else 

          headers = baos.toString(); 

          return headers; 

          readHeaders方法循環(huán)調(diào)用readByte()方法逐個讀取buffer緩沖區(qū)中的字節(jié),并將讀取的字節(jié)與HEADER_SEPARATOR ={‘\n’,‘\r’,‘\n’ 

          ,‘\r’}的第一個字節(jié)進(jìn)行比較,如果這個字節(jié)等于HEADER_SEPARATOR的首字節(jié)‘\n’,則循環(huán)控制因子i加1,這樣,下次調(diào)用readByte()方 

          法讀取的字節(jié)將與HEADER_SEPARATOR中的第二字節(jié)比較,如果相等,則依照這種方式比較后面的字節(jié)內(nèi)容,如果連續(xù)讀取到了 

          HEADER_SEPARATOR字節(jié)序列,則循環(huán)語句結(jié)束。readHeaders方法將讀取到的每個正常字節(jié)寫入到了一個字節(jié)數(shù)組輸出流中,其中也包括作為描 

          述頭與主體內(nèi)容之間的分隔序列HEADER_SEPARATOR中的字節(jié)數(shù)據(jù)。由于readByte()方法會自動移動head變量的值和自動向緩沖區(qū)buffer中載入 

          數(shù)據(jù),所以,readHeaders方法執(zhí)行完以后,成員變量head指向分區(qū)主體部分的首字節(jié)。readHeaders方法最后將把存入字節(jié)數(shù)組輸出流中的字 

          節(jié)數(shù)據(jù)按指定字符集編碼轉(zhuǎn)換成字符串并返回,該字符串就是描述頭字符串。 

          8. readBodyData方法 
          MultipartStream類中的readBodyData方法用于把主體部分的數(shù)據(jù)寫入到一個輸出流對象中,并返回寫入到輸出流中的字節(jié)總數(shù)。當(dāng)調(diào)用 

          readBodyData方法前,成員變量head已經(jīng)指向了分區(qū)的主體部分的首字節(jié),readBodyData方法調(diào)用完成后,成員變量head指向分區(qū)分隔界線的 

          首字節(jié)。readBodyData方法中需要調(diào)用findSeparator方法找出下一個分區(qū)分隔界線的首字節(jié)位置,才能知道這次讀取的分區(qū)主體內(nèi)容的結(jié)束位 

          置。從分區(qū)主體部分的首字節(jié)開始,直到在findSeparator方法找到的下一個分區(qū)分隔界線前的所有數(shù)據(jù)都是這個分區(qū)的主體部分的數(shù)據(jù), 

          readBodyData方法需要把這些數(shù)據(jù)都寫到輸出流output對象中。如果findSeparator方法在buffer緩沖區(qū)中沒有找到分區(qū)分隔界線, 

          readBodyData方法還必須向buffer緩沖區(qū)中裝入新的數(shù)據(jù)內(nèi)容后繼續(xù)調(diào)用findSeparator方法進(jìn)行處理。在向buffer緩沖區(qū)中裝入新的數(shù)據(jù)內(nèi)容 

          時,必須先將上次保留在buffer緩沖區(qū)中的內(nèi)容轉(zhuǎn)移進(jìn)新buffer緩沖區(qū)的開始處。readBodyData方法的源代碼如下,傳遞給readBodyData方法 

          的參數(shù)實際上是一個DeferredFileOutputStream類對象: 
          public int readBodyData(OutputStream output) 
          throws MalformedStreamException,IOException 

          // 用于控制循環(huán)的變量 
          boolean done = false; 
          int pad; 
          int pos; 
          int bytesRead; 
          // 寫入到輸出流中的字節(jié)個數(shù) 
          int total = 0; 
          while (!done) 

          pos = findSeparator();// 搜索分隔界線 
          if (pos != -1) //緩沖區(qū)buffer中包含有分隔界線 

          output.write(buffer, head, pos - head); 
          total += pos - head; 
          head = pos;//head變量跳過主體數(shù)據(jù),指向分隔界線的首字節(jié) 
          done = true;// 跳出循環(huán) 

          else //緩沖區(qū)buffer中沒有包含分隔界線 

          /*根據(jù)緩沖區(qū)中未被readHeaders方法讀取的數(shù)據(jù)內(nèi)容是否大于圖1.4中的 
          保留區(qū)的大小,來決定保留到下一次buffer緩沖區(qū)中的字節(jié)個數(shù) 
          */ 
          if (tail - head > keepRegion) 

          pad = keepRegion; 

          else 

          pad = tail - head; 

          output.write(buffer, head, tail - head - pad); 
          total += tail - head - pad;//統(tǒng)計寫入到輸出流中的字節(jié)個數(shù) 
          /*將上一次buffer緩沖區(qū)中的未處理的數(shù)據(jù)轉(zhuǎn)移到 
          下一次buffer緩沖區(qū)的開始位置 
          */ 
          System.arraycopy(buffer, tail - pad, buffer, 0, pad); 
          head = 0; //讓head變量指向緩沖區(qū)的開始位置 
          //向buffer緩沖區(qū)中載入新的數(shù)據(jù) 
          bytesRead = input.read(buffer, pad, bufSize - pad); 
          if (bytesRead != -1) 

          //設(shè)置buffer緩沖區(qū)中的有效字節(jié)的個數(shù) 
          tail = pad + bytesRead; 

          else 

          /*還沒有找到分隔界線,輸入流就結(jié)束了,輸入流中的數(shù)據(jù)格式 
          顯然不正確,保存緩沖區(qū)buffer中還未處理的數(shù)據(jù)后拋出異常 
          */ 
          output.write(buffer, 0, pad); 
          output.flush(); 
          total += pad; 
          throw new MalformedStreamException 
          ("Stream ended unexpectedly "); 



          output.flush(); 
          return total; 


          9. discardBodyData方法 
          MultipartStream類中的discardBodyData方法用來跳過主體數(shù)據(jù),它與readBodyData方法非常相似,不同之處在于readBodyData方法把數(shù)據(jù)寫 

          入到一個輸出流中,而discardBodyData方法是把數(shù)據(jù)丟棄掉。discardBodyData方法返回被丟掉的字節(jié)個數(shù),方法調(diào)用完成后成員變量head指 

          向下一個分區(qū)分隔界線的首字節(jié)。MultipartStream類中定義discardBodyData這個方法,是為了忽略主體內(nèi)容部分的第一個分隔界線前面的內(nèi) 

          容,按照MIME規(guī)范,消息頭和消息體之間的分隔界線前面可以有一些作為注釋信息的內(nèi)容,discardBodyData就是為了拋棄這些注釋信息而提供 

          的。discardBodyData方法的源代碼如下: 
          public int discardBodyData() throws MalformedStreamException,IOException 

          boolean done = false; 
          int pad; 
          int pos; 
          int bytesRead; 
          int total = 0; 
          while (!done) 

          pos = findSeparator(); 
          if (pos != -1) 

          total += pos - head; 
          head = pos; 
          done = true; 

          else 

          if (tail - head > keepRegion) 

          pad = keepRegion; 

          else 

          pad = tail - head; 

          total += tail - head - pad; 
          System.arraycopy(buffer, tail - pad, buffer, 0, pad); 
          head = 0; 
          bytesRead = input.read(buffer, pad, bufSize - pad); 
          if (bytesRead != -1) 

          tail = pad + bytesRead; 

          else 

          total += pad; 
          throw new MalformedStreamException 
          ("Stream ended unexpectedly "); 



          return total; 

          10. readBoundary方法 
          對于圖1.3中的每一個分區(qū)的解析處理,程序首先要調(diào)用readHeaders方法讀取描述頭,接著要調(diào)用readBodyData(OutputStream output)讀取主 

          體數(shù)據(jù),這樣就完成了一個分區(qū)的解析。readBodyData方法內(nèi)部調(diào)用findSeparator方法找到了分隔界線,然后讀取分隔界線前面的內(nèi)容,此時 

          MultipartStream類中的成員變量head指向了buffer緩沖區(qū)中的分隔界線boundary的第一個字節(jié)。findSeparator方法只負(fù)責(zé)尋找分隔界線 

          boundary在緩沖區(qū)buffer中的位置,不負(fù)責(zé)從buffer緩沖區(qū)中讀走分隔界線的字節(jié)數(shù)據(jù)。在調(diào)用readBodyData方法之后,程序接著應(yīng)該讓成員 

          變量head跳過分隔界線,讓它指向下一個分區(qū)的描述頭的第一個字節(jié),才能調(diào)用readHeaders方法去讀取下一個分區(qū)的描述頭。 
          MultipartStream類中定義了一個readBoundary方法,用于讓成員變量head跳過分隔界線,讓它指向下一個分區(qū)的描述頭的第一個字節(jié)。對于圖 

          1.3中的最后的分隔界線,它比其他的分隔界線后面多了兩個“-”字符,而其他分隔界線與下一個分區(qū)的內(nèi)容之間還有一個回車換行,所以, 

          readBoundary方法內(nèi)部跳過分隔界線后,還需要再讀取兩個字節(jié)的數(shù)據(jù),才能讓成員變量head指向下一個分區(qū)的描述頭的第一個字節(jié)。 

          readBoundary方法內(nèi)部讀取分隔界線后面的兩個字節(jié)數(shù)據(jù)后,根據(jù)它們是回車換行、還是兩個“-”字符,來判斷這個分隔界線是下一個分區(qū)的 

          開始標(biāo)記,還是整個請求消息的實體內(nèi)容的結(jié)束標(biāo)記。如果readBoundary方法發(fā)現(xiàn)分隔界線是下一個分區(qū)的開始標(biāo)記,那么它返回true,否則 

          返回false。readBoundary()方法的源代碼如下: 
          public boolean readBoundary()throws MalformedStreamException 

          byte[] marker = new byte[2]; 
          boolean nextChunk = false; 
          head += boundaryLength; // 跳過分隔界線符 
          try 

          marker[0] = readByte(); 
          marker[1] = readByte(); 
          // 靜態(tài)常量STREAM_TERMINATOR ={‘-’、‘-’} 
          if (arrayequals(marker, STREAM_TERMINATOR, 2)) 

          nextChunk = false; 

          // 靜態(tài)常量FIELD_SEPARATOR ={‘/n’、‘/r’} 
          else if (arrayequals(marker, FIELD_SEPARATOR, 2)) 

          nextChunk = true; 

          else 

          /*如果讀到的既不是回車換行,又不是兩個減號, 
          說明輸入流有問題,則拋異常。 

          posted on 2011-12-07 20:08 Solitary 閱讀(2226) 評論(1)  編輯  收藏

          評論

          # re: Apache Common fileUpload API 詳解(轉(zhuǎn))[未登錄] 2013-09-24 18:51 小鳥

          最近項目中用到了關(guān)于文件上傳的相關(guān)知識,我是新手。
          想和您學(xué)習(xí)一下。
          我要實現(xiàn)多文件上傳,要對單個文件進(jìn)行文件大小的檢驗,單獨使用setFileMaxSize的話,如果單個文件過大,檢驗時很耽誤時間,我怎么能把request中的多個文件提取出來呢? 謝謝。
          我的聯(lián)系方式y(tǒng)xm_meng@163.com  回復(fù)  更多評論   


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          <2013年9月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          導(dǎo)航

          統(tǒng)計

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 龙山县| 金阳县| 平潭县| 泸溪县| 柘荣县| 新竹市| 呼伦贝尔市| 江源县| 会昌县| 泸定县| 北流市| 澄迈县| 宜州市| 习水县| 龙门县| 石城县| 大田县| 抚宁县| 广南县| 汝南县| 梅州市| 临江市| 喀喇| 鹤壁市| 卢氏县| 农安县| 达尔| 长春市| 宣恩县| 白银市| 韶山市| 甘泉县| 铁岭市| 中方县| 江口县| 赤水市| 安康市| 宜君县| 庆城县| 保亭| 和田县|