天天看點

《Spring 5 官方文檔》4. 資源(一)

僅僅使用 java 的 java.net.url 和針對不同 url 字首的标準處理器,并不能滿足我們對各種底層資源的通路,比如:我們就不能通過 url 的标準實作來通路相對類路徑或者相對 servletcontext 的各種資源。雖然我們可以針對特定的 url 字首來注冊一個新的 urlstreamhandler(和現有的針對各種特定字首的處理器類似,比如 http:),然而這往往會是一件比較麻煩的事情(要求了解 url 的實作機制等),而且 url 接口也缺少了部分基本的方法,如檢查目前資源是否存在的方法。

相對标準 url 通路機制,spring 的 resource 接口對抽象底層資源的通路提供了一套更好的機制。

resource 接口裡的最重要的幾個方法:

getinputstream(): 定位并且打開目前資源,傳回目前資源的 inputstream。預計每一次調用都會傳回一個新的 – inputstream,是以關閉目前輸出流就成為了調用者的責任。

exists(): 傳回一個 boolean,表示目前資源是否真的存在。

isopen(): 傳回一個 boolean,表示目前資源是否一個已打開的輸入流。如果結果為 true,傳回的 inputstream 不能多次讀取,隻能是一次性讀取之後,就關閉 inputstream,以防止記憶體洩漏。除了 inputstreamresource,其他常用 resource 實作都會傳回 false。

getdescription(): 傳回目前資源的描述,當處理資源出錯時,資源的描述會用于錯誤資訊的輸出。一般來說,資源的描述是一個完全限定的檔案名稱,或者是目前資源的真實 url。

resource 接口裡的其他方法可以讓你獲得代表目前資源的 url 或 file 對象(前提是底層實作可相容的,也支援該功能)。

resource抽象在spring本身被廣泛使用,作為需要資源的許多方法簽名中的參數類型。 某些spring api中的其他方法(例如各種applicationcontext實作的構造函數)采用一個string,它以未安裝或簡單的形式用于建立适用于該上下文實作的資源,或者通過string路徑上的特殊字首,允許調用者 以指定必須建立和使用特定的資源實作。

resource 接口(實作)不僅可以被 spring 大量的應用,其也非常适合作為你程式設計中通路資源的輔助工具類。當你僅需要使用到 resource 接口實作時,可以直接忽略 spring 的其餘部分。單獨使用 rsourece 實作,會造成代碼與 spring 的部分耦合,可也僅耦合了其中一小部分輔助類,而且你可以将 reource 實作作為 url 的一種通路底層更為有效的替代,與你引入其他庫來達到這種目的是一樣的。

需要注意的是 resource 實作并沒有去重新發明輪子,而是盡可能地采用封裝。舉個例子,urlresource 裡就封裝了一個 url 對象,在其内的邏輯就是通過封裝的 url 對象來完成的。

spring 直接提供了多種開箱即用的 resource 實作。

urlresource 封裝了一個 java.net.url 對象,用來通路 url 可以正常通路的任意對象,比如檔案、an http target, an ftp target, 等等。所有的 url 都可以用一個标準化的字元串來表示。如通過正确的标準化字首,可以用來表示目前 url 的類型,當中就包括用于通路檔案系統路徑的 file:,通過 http 協定通路資源的 http:,通過 ftp 協定通路資源的 ftp:,還有很多……

可以顯式化地使用 urlresource 構造函數來建立一個 urlresource,不過通常我們可以在調用一個 api 方法是,使用一個代表路徑的 string 參數來隐式建立一個 urlresource。對于後一種情況,會由一個 javabean propertyeditor 來決定建立哪一種 resource。如果路徑裡包含某一個通用的字首(如 classpath:),propertyeditor 會根據這個通用的字首來建立恰當的 resource;反之,如果 propertyeditor 無法識别這個字首,會把這個路徑作為一個标準的 url 來建立一個 urlresource。

classpathresource 可以從類路徑上加載資源,其可以使用線程上下文加載器、指定加載器或指定的 class 類型中的任意一個來加載資源。

當類路徑上資源存于檔案系統中,classpathresource 支援以 java.io.file 的形式通路,可當類路徑上的資源存于尚未解壓(沒有 被servlet 引擎或其他可解壓的環境解壓)的 jar 包中,classpathresource 就不再支援以 java.io.file 的形式通路。鑒于上面所說這個問題,spring 中各式 resource 實作都支援以 jave.net.url 的形式通路。

可以顯式使用 classpathresource 構造函數來建立一個 classpathresource ,不過通常我們可以在調用一個 api 方法時,使用一個代表路徑的 string 參數來隐式建立一個 classpathresource。對于後一種情況,會由一個 javabean propertyeditor 來識别路徑中 classpath: 字首,進而建立一個 classpathresource。

這是針對 java.io.file 提供的 resource 實作。顯然,我們可以使用 filesystemresource 的 getfile() 函數擷取 file 對象,使用 geturl() 擷取 url 對象。

這是為了擷取 web 根路徑的 servletcontext 資源而提供的 resource 實作。

servletcontextresource 完全支援以流和 url 的方式通路,可隻有當 web 項目是已解壓的(不是以 war 等壓縮包形式存在)且該 servletcontext 資源存于檔案系統裡,servletcontextresource 才支援以 java.io.file 的方式通路。至于說到,我們的 web 項目是否已解壓和相關的 servletcontext 資源是否會存于檔案系統裡,這個取決于我們所使用的 servlet 容器。若 servlet 容器沒有解壓 web 項目,我們可以直接以 jar 的形式的通路,或者其他可以想到的方式(如通路資料庫)等。

這是針對 inputstream 提供的 resource 實作。建議,在确實沒有找到其他合适的 resource 實作時,才使用 inputsteamresource。如果可以,盡量選擇 bytearrayresource 或其他基于檔案的 resource 實作來代替。

與其他 resource 實作已比較,inputstreamrsource 倒像一個已打開資源的描述符,是以,調用 isopen() 方法會傳回 true。除了在需要擷取資源的描述符或需要從輸入流多次讀取時,都不要使用 inputstreamresource 來讀取資源。

這是針對位元組數組提供的 resource 實作。可以通過一個位元組數組來建立 bytearrayresource。

當需要從位元組數組加載内容時,bytearrayresource 是一個不錯的選擇,使用 bytearrayresource 可以不用求助于 inputstreamresource。

resourceloader 接口是用來加載 resource 對象的,換句話說,就是當一個對象需要擷取 resource 執行個體時,可以選擇實作 resourceloader 接口。

spring 裡所有的應用上下文都是實作了 resourceloader 接口,是以,所有應用上下文都可以通過 getresource() 方法擷取 resource 執行個體。

當你在指定應用上下文調用 getresource() 方法時,而指定的位置路徑又沒有包含特定的字首,spring 會根據目前應用上下文來決定傳回哪一種類型 resource。舉個例子,假設下面的代碼片段是通過 classpathxmlapplicationcontext 執行個體來調用的,

那 spring 會傳回一個 classpathresource 對象;類似的,如果是通過執行個體 filesystemxmlapplicationcontext 執行個體調用的,傳回的是一個 filesystemresource 對象;如果是通過 webapplicationcontext 執行個體的,傳回的是一個 servletcontextresource 對象…… 如上所說,你就可以在指定的應用上下中使用 resource 執行個體來加載目前應用上下文的資源。

還有另外一種場景裡,如在其他應用上下文裡,你可能會強制需要擷取一個 classpathresource 對象,這個時候,你可以通過加上指定的字首來實作這一需求,如:

類似的,你可以通過其他任意的 url 字首來強制擷取 urlresource 對象:

下面,給出一個表格來總結一下 spring 根據各種位置路徑加載資源的政策:

字首

例子

解釋

classpath:

classpath:com/myapp/config.xml

從類路徑加載

file:

<a href="http://data/config.xml">file:///data/config.xml</a>

以url形式從檔案系統加載

http:

<a href="http://myserver/logo.png">http://myserver/logo.png</a>

以url形式加載

(none)

/data/config.xml

由底層的applicationcontext實作決定

table 4.1. resource strings

resourceloaderaware 是一個特殊的标記接口,用來标記提供 resourceloader 引用的對象。

當将一個 resourceloaderaware 接口的實作類部署到應用上下文時(此類會作為一個 spring 管理的 bean), 應用上下文會識别出此為一個 resourceloaderaware 對象,并将自身作為一個參數來調用 setresourceloader() 函數,如此,該實作類便可使用 resourceloader 擷取 resource 執行個體來加載你所需要的資源。(附:為什麼能将應用上下文作為一個參數來調用 setresourceloader() 函數呢?不要忘了,在前文有談過,spring 的所有上下文都實作了 resourceloader 接口)。

當然了,一個 bean 若想加載指定路徑下的資源,除了剛才提到的實作 resourcesloaderaware 接口之外(将 applicationcontext 作為一個 resourceloader 對象注入),bean 也可以實作 applicationcontextaware 接口,這樣可以直接使用應用上下文來加載資源。但總的來說,在需求滿足都滿足的情況下,最好是使用的專用 resourceloader 接口,因為這樣代碼隻會與接口耦合,而不會與整個 spring applicationcontext 耦合。與 resourceloader 接口耦合,抛開 spring 來看,就是提供了一個加載資源的工具類接口。

從 spring 2.5 開始,除了實作 resourceloaderaware 接口,也可采取另外一種替代方案——依賴于 resourceloader 的自動裝配。”傳統”的 constructor 和 bytype 自動裝配模式都支援 resourceloader 的裝配(可參閱 section 5.4.5, “自動裝配協作者” )——前者以構造參數的形式裝配,後者以 setter 方法中參數裝配。若為了獲得更大的靈活性(包括屬性注入的能力和多參方法),可以考慮使用基于注解的新注入方式。使用注解 @autowiring 标記 resourceloader 變量,便可将其注入到成員屬性、構造參數或方法參數中( @autowiring 詳細的使用方法可參考section 3.9.2, “@autowired”.)。