天天看點

SpringMVC檔案上傳接口設計與實作1 前兩篇文章的鋪墊1.1 SpringMVC檔案上傳源碼分析前言 1.2 apache fileupload源碼分析 2 整體的包結構3 SpringMVC自己的接口設計4 整合apache fileupload對檔案上傳的解析5 整合j2ee自帶的檔案上傳的解析

首先看下整體的包的結構,如下圖

SpringMVC檔案上傳接口設計與實作1 前兩篇文章的鋪墊1.1 SpringMVC檔案上傳源碼分析前言 1.2 apache fileupload源碼分析 2 整體的包結構3 SpringMVC自己的接口設計4 整合apache fileupload對檔案上傳的解析5 整合j2ee自帶的檔案上傳的解析

總共分成3大塊,分别如下

存放spring定義的檔案上傳接口以及異常,如

multipartexception對使用者抛出的解析異常(隐藏底層檔案上傳解析包所抛出的異常)

也就指明了,這個體系下隻能抛出這種類型的異常,maxuploadsizeexceededexception是multipartexception它的子類,專門用于指定檔案大小限制的異常。

使用者不應該看到底層檔案上傳解析包所抛出的異常,底層采用的檔案上傳解析包在解析檔案上傳時也會定義自己的解析異常,這時候就需要在整合這些jar包時,需要對解析包所抛出的異常進行轉換成上述已統一定義的面向使用者的異常

源碼見證下:

fileuploadbase.sizelimitexceededexception、fileuploadexception 都是底層解析包apache fileupload解析時抛出的異常,在這裡要進行try catch 處理,然後将這些異常轉化成springmvc自定義的異常maxuploadsizeexceededexception、multipartexception

multipartfile 定義了檔案解析的統一結果類型

multipartresolver 定義了檔案解析的處理器,不同的處理器不同的解析方式

用于整合apache fileupload的解析,對上述定義的接口進行實作,如

commonsmultipartfile實作上述multipartfile接口,即采用apache fileupload解析的結果為commonsmultipartfile

commonsmultipartresolver實作上述multipartresolver,待會詳細說明

用于整合j2ee自帶的檔案上傳的解析,對上述定義的接口進行實作,如

standardmultipartfile實作上述multipartfile接口,即采用這種方式解析的結果為standardmultipartfile

standardservletmultipartresolver實作上述multipartresolver,待會詳細說明

接下來詳細看看這些源碼内容

multiparthttpservletrequest 繼承了 httpservletrequest 和 multipartrequest,然後就具有了下面的兩個主要功能

擷取檔案上傳的每一部分的請求頭資訊

這裡的請求頭資訊就是如下内容中的 content-disposition: form-data; name=“myfile”; filename=“資産型号規格模闆1.xlsx” 

content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet 等資訊

SpringMVC檔案上傳接口設計與實作1 前兩篇文章的鋪墊1.1 SpringMVC檔案上傳源碼分析前言 1.2 apache fileupload源碼分析 2 整體的包結構3 SpringMVC自己的接口設計4 整合apache fileupload對檔案上傳的解析5 整合j2ee自帶的檔案上傳的解析

擷取檔案上傳的檔案内容(每個檔案資訊都是multipartfile類型)

在springmvc的入口類dispatcherservlet中的dodispatch方法中,可以看到是如下的處理流程

可以看到這裡主要有兩個步驟

步驟一 檢查是否是檔案上傳類型,如果是則進行解析,然後将httpservletrequest request封裝成multiparthttpservletrequest

步驟二 如果是檔案上傳,則進行資源清理,如删除上傳的臨時檔案等

下面分别來說

首先看看dispatcherservlet的multipartresolver屬性是否有值,而我們在xml檔案中如下的配置就是向dispatcherservlet注入multipartresolver屬性

dispatcherservlet在初始化的時候,會去尋找id為"multipartresolver"并且類型為multipartresolver的bean,是以id必須為multipart_resolver_bean_name即"multipartresolver”,如下

}

當multipartresolver屬性有值的時候,先調用它的boolean ismultipart(httpservletrequest request)方法,判斷目前的request是否是符合檔案上傳類型,如果符合則調用它的multiparthttpservletrequest resolvemultipart(httpservletrequest request)方法将目前的request進行解析并且封裝成multiparthttpservletrequest類型。有了multiparthttpservletrequest,我們就能擷取上傳的檔案資訊了。

然後我們就可以通過2中途徑來擷取上傳的檔案。

途徑1 直接使用multiparthttpservletrequest request作為參數,如下

途徑2 使用@requestparam(“myfile”) 來擷取檔案(requestparam裡面的"myfile"是input标簽的name的值而不是檔案名),如下

對于途徑1很好了解,對于途徑2,為什麼呢?

這裡簡單提下,對于@requestparam注解是由requestparammethodargumentresolver來進行處理的,是它進行了特殊處理,當@requestparam修飾的類型為multipartfile或者javax.servlet.http.part(後面再詳細說此part)時進行特殊處理,如下

我們這裡可以看到,其實也是通過multiparthttpservletrequest的getfile等方法來擷取的,同時支援數組、集合形式的參數

這裡其實就是調用multipartresolver接口的void cleanupmultipart(multiparthttpservletrequest request)方法

至此springmvc已經完成了自己的檔案上傳架構體系,即底層不管采用何種檔案解析包都是走這樣的一個流程。這樣的一個流程其實就是對實際業務的抽象過程。我們在寫代碼的時候,經常就缺少抽象的能力,即很少抽象出各種業務邏輯的共同點。

剛才說了整個檔案上傳的處理流程,然後我們就來看下apache fileupload是如何整合進來的。即commonsmultipartresolver是如何實作的

這裡就是使用apache fileupload自己的servletfileupload.ismultipartcontent判斷方法,上一篇文章已經講述了,這裡不再說明了。

這裡我們可以再多想一下,功能的職責劃分問題(雖然問題很簡單,主要是想引導大家在寫代碼的時候多去思考)。

因為目前判斷一個request是否是multipart形式,都是一樣的,不管你是哪種解析包,為什麼springmvc不統一進行判斷,而是采用解析包的判斷?

如果springmvc自己進行統一的判斷,似乎也沒什麼問題。站在apache fileupload的角度來說,判斷request是否是multipart形式 的确應該是它的一個功能,而不是等待外界來判斷。

springmvc既然采用第三方的解析包,就要遵守人家解析包的判斷邏輯,而不是自行判斷,雖然他們目前的判斷邏輯是一樣的。萬一後來又出來一個解析包,判斷邏輯不一樣呢?如果流程體系還是采用springmvc自己的判斷,可能就沒法正常解析了

一旦上述判斷通過了,則就需要執行解析過程(可以立即解析,也可以延遲解析),看下具體的解析過程

這裡大緻說下過程,詳細的内容去看源代碼。

使用apache fileupload的servletfileupload對request進行解析,解析結果為list ,代碼如下:

fileitem為apache fileupload自己的解析結果,需要轉化為springmvc自己定義的multipartfile

這裡有普通字段的處理和檔案字段的處理。還記得上文講的org.springframework.web.multipart.commons包的commonsmultipartfile嗎?可以看到通過new commonsmultipartfile(fileitem),就将fileitem結果轉化為了multipartfile結果。

至此就将httpservletrequest解析成了defaultmultiparthttpservletrequest,是以我們在使用request時,它的類型其實就是defaultmultiparthttpservletrequest類型,我們可以通過它來擷取各種上傳的檔案資訊。

其實就是對所有的commonsmultipartfile中的fileitem進行删除臨時檔案的操作,這個删除操作是apache fileupload自己定義的,如下

至此,springmvc與apache fileupload的整合完成了,其他的整合也是類似的操作。

這個不再詳細說明,主要引出來 javax.servlet.http.part 這個對象是j2ee内置的檔案上傳解析結果,類似apache fileupload的fileitem解析結果,從servlet3.0才加入進來的。

和apache fileupload一樣的步驟,來看下具體源碼内容:

同樣是這兩個條件,post和"multipart/“開頭。

在建立standardmultiparthttpservletrequest的時候進行解析,解析過程和apache fileupload非常類似,隻不過用part替代了apache fileupload的fileitem,如下

周遊所有的part,把每一個part轉化成standardmultipartfile,而apache fileupload則是轉化成commonsmultipartfile。不再詳細說明,具體的可以去看源碼。

這裡還有很多小插曲。

我之前導入的一直是

之後把它換成3點多的版本,還是沒找到javax.servlet.http.part,最後才發現導入的是下面的形式

這裡的scope是provided,即不再加入運作環境,直接使用tomcat容器自身的servlet-api。目前我的tomcat7中servlet-api.jar包是含有這個javax.servlet.http.part對象的,是以是可以的

然後我就替換掉apache fileupload,使用servlet3自帶的part功能,來使用檔案上傳,發現不行,沒有得到解析結果,就想嘗試調試下,然而運作到collection parts = request.getparts()這裡的時候,就不能檢視源檔案了,這裡的request是org.apache.catalina.connector.requestfacade類型,沒有關聯到源檔案,經過一番尋找,最終找到tomcat的maven依賴

有了它,我們就可以在調試的時候,檢視tomcat内部的運作情況了

然後一路跟蹤,定位到結果為 需要将org.apache.catalina.core.standardcontext的allowcasualmultipartparsing屬性設定為true,即允許進行檔案解析,預設為false。需要在server.xml中修改工程配置,然後就大功告成了。