先來看下含有檔案上傳時的表單送出是怎樣的格式
form表單送出内容如下
從上面可以看到,含有檔案上傳的格式是這樣組織的。
檔案類型字段
其他類型字段
結束
對于上面的檔案内容,chrome浏覽器是不顯示的,換成firefox可以看到,如下圖所示
同時我們還可以注意到,不同的浏覽器,分隔符是不一樣的,在請求頭
中指明了分隔符的内容。
一定是post送出,如果換成get送出,則浏覽器預設僅僅把檔案名作為屬性值來上傳,不會上傳檔案内容,如下
form表單中一定不要忘了添加
否則的話,浏覽器則不是按照上述的格式來來傳遞資料的。
上述兩點才能保證浏覽器正常的進行檔案上傳。
有了上述檔案上傳的組織格式,我們就需要合理的設計背景的解析方式,下面來看下apache fileupload的使用。先來看下整體的流程圖
apache fileupload分servlets and portlets兩種情形來處理。servlet我們很熟悉,而portlets我也沒用過,可自行去搜尋。
對于httpservletrequest來說,另一個不再說明,自行檢視源碼,判斷規則如下:
是否是post請求
contenttype是否以multipart/開頭
見源碼:
servlet的輸入參數為httpservletrequest,portlets的輸入參數為actionrequest,資料來源不同,為了統一友善後面的資料處理,引入了requestcontext接口,來統一一下目标資料的擷取。
接口requestcontext的實作類:
servletrequestcontext
portletrequestcontext
此時requestcontext就作為了資料源,不再與httpservletrequest和actionrequest打交道。
上述的實作過程是由fileupload的子類servletfileupload和portletfileupload分别完成包裝的。
父類fileupload的子類:
servletfileupload
portletfileupload
源碼展示如下:
servletfileupload類
portletfileupload類
上述的parserequest便完成了整個request的解析過程,内容如下:
分以下兩個大步驟:
根據requestcontext資料源得到解析後的資料集合 fileitemiterator
周遊fileitemiterator中的每個item,類型為fileitemstreamimpl,使用fileitemfactory工廠類來将每個fileitemstreamimpl轉化成最終的fileitem
fileitemiterator内容如下:
這就是一個輪詢器,可以假想成fileitemstream的集合,實際上不是,後面會進行介紹
fileitemstream則是之前上傳檔案格式内容
或者
的封裝,代碼如下
然後我們來具體看下由requestcontext如何解析成一個fileitemiterator的:
new了一個fileitemiteratorimpl,來看下具體的過程:
要點:
contenttype進行判斷,是否以multipart開頭
判斷整個請求流的資料大小是否超過sizemax最大設定
擷取重要的分隔符boundary資訊
封裝了request請求流的資料,包裝為multipartstream類型
也可以設定通知器,來通知流的讀取進度
這裡可以看到fileitemiteratorimpl并不是fileitemstreamimpl的集合,其實是fileitemiteratorimpl内部包含了一個fileitemstreamimpl屬性。fileitemiteratorimpl的一些重要屬性和方法如下:
findnextitem()方法就是建立新的fileitemstreamimpl來替代目前的fileitemstreamimpl,并更新起始位置。
每次調用fileitemiteratorimpl的hasnext()方法,會建立一個新的fileitemstreamimpl指派給fileitemstreamimpl屬性
每次調用fileitemiteratorimpl的next()方法,就會傳回目前fileitemstreamimpl屬性的值
建立的每個fileitemstreamimpl都會共享fileitemiteratorimpl的multipartstream總流,僅僅更新了要讀取的起始位置
其他應用其實就可以周遊fileitemiteratorimpl拿到每一項fileitemstreamimpl的解析資料了。隻是這時候資料
存儲在記憶體中的
每個fileitemstreamimpl都是共享一個總的流,不能被重複讀取
我們想把這些檔案資料存在臨時檔案中,就需要使用使用fileitemfactory來進行下轉化成fileitem。每個fileitem才是互相獨立的,而fileitemstreamimpl則不是,每個fileitem也是對應上傳檔案格式中的每一項,如下
fileitemfactory的實作類diskfileitemfactory即将資料存儲在硬碟上,代碼如下:
我們從上面可以看到,其實fileitemfactory的createitem方法,并沒有為fileitem的流指派。再回顧下上文parserequest方法的源代碼,指派發生在這裡
上述fileitem的openstream()方法如下:
gettempfile()會根據fileitemfactory的臨時檔案目錄配置repository,建立一個臨時檔案,用于上傳檔案。
這裡又用到了commons-io包中的deferredfileoutputstream類。
當資料數量小于sizethreshold門檻值時,存儲在記憶體中
當資料數量大于sizethreshold門檻值時,存儲到傳入的臨時檔案中
至此,fileitem都被建立出來了,整個過程就結束了。