《Spring 5 官方文檔》4. 資源(一)
4.1 介紹
僅僅使用 JAVA 的 java.net.URL 和針對不同 URL 前綴的標準處理器,並不能滿足我們對各種底層資源的訪問,比如:我們就不能通過 URL 的標準實現來訪問相對類路徑或者相對 ServletContext 的各種資源。雖然我們可以針對特定的 url 前綴來注冊一個新的 URLStreamHandler(和現有的針對各種特定前綴的處理器類似,比如 http:),然而這往往會是一件比較麻煩的事情(要求了解 url 的實現機製等),而且 url 接口也缺少了部分基本的方法,如檢查當前資源是否存在的方法。
4.2 Resource 接口
相對標準 url 訪問機製,spring 的 Resource 接口對抽象底層資源的訪問提供了一套更好的機製。
public interface Resource extends InputStreamSource {
boolean exists();
boolean isOpen();
URL getURL() throws IOException;
File getFile() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
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 對象來完成的。
4.3 內置的 Resource 實現
spring 直接提供了多種開箱即用的 Resource 實現。
4.3.1 UrlResource
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。
4.3.2 ClassPathResource
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。
4.3.3 FileSystemResource
這是針對 java.io.File 提供的 Resource 實現。顯然,我們可以使用 FileSystemResource 的 getFile() 函數獲取 File 對象,使用 getURL() 獲取 URL 對象。
4.3.4 ServletContextResource
這是為了獲取 web 根路徑的 ServletContext 資源而提供的 Resource 實現。
ServletContextResource 完全支持以流和 URL 的方式訪問,可隻有當 web 項目是已解壓的(不是以 war 等壓縮包形式存在)且該 ServletContext 資源存於文件係統裏,ServletContextResource 才支持以 java.io.File 的方式訪問。至於說到,我們的 web 項目是否已解壓和相關的 ServletContext 資源是否會存於文件係統裏,這個取決於我們所使用的 Servlet 容器。若 Servlet 容器沒有解壓 web 項目,我們可以直接以 JAR 的形式的訪問,或者其他可以想到的方式(如訪問數據庫)等。
4.3.5 InputStreamResource
這是針對 InputStream 提供的 Resource 實現。建議,在確實沒有找到其他合適的 Resource 實現時,才使用 InputSteamResource。如果可以,盡量選擇 ByteArrayResource 或其他基於文件的 Resource 實現來代替。
與其他 Resource 實現已比較,InputStreamRsource 倒像一個已打開資源的描述符,因此,調用 isOpen() 方法會返回 true。除了在需要獲取資源的描述符或需要從輸入流多次讀取時,都不要使用 InputStreamResource 來讀取資源。
4.3.6 ByteArrayResource
這是針對字節數組提供的 Resource 實現。可以通過一個字節數組來創建 ByteArrayResource。
當需要從字節數組加載內容時,ByteArrayResource 是一個不錯的選擇,使用 ByteArrayResource 可以不用求助於 InputStreamResource。
4.4 ResourceLoader 接口
ResourceLoader 接口是用來加載 Resource 對象的,換句話說,就是當一個對象需要獲取 Resource 實例時,可以選擇實現 ResourceLoader 接口。
public interface ResourceLoader {
Resource getResource(String location);
}
spring 裏所有的應用上下文都是實現了 ResourceLoader 接口,因此,所有應用上下文都可以通過 getResource() 方法獲取 Resource 實例。
當你在指定應用上下文調用 getResource() 方法時,而指定的位置路徑又沒有包含特定的前綴,spring 會根據當前應用上下文來決定返回哪一種類型 Resource。舉個例子,假設下麵的代碼片段是通過 ClassPathXmlApplicationContext 實例來調用的,
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
那 spring 會返回一個 ClassPathResource 對象;類似的,如果是通過實例 FileSystemXmlApplicationContext 實例調用的,返回的是一個 FileSystemResource 對象;如果是通過 WebApplicationContext 實例的,返回的是一個 ServletContextResource 對象…… 如上所說,你就可以在指定的應用上下中使用 Resource 實例來加載當前應用上下文的資源。
還有另外一種場景裏,如在其他應用上下文裏,你可能會強製需要獲取一個 ClassPathResource 對象,這個時候,你可以通過加上指定的前綴來實現這一需求,如:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
類似的,你可以通過其他任意的 url 前綴來強製獲取 UrlResource 對象:
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
下麵,給出一個表格來總結一下 spring 根據各種位置路徑加載資源的策略:
前綴 | 例子 | 解釋 |
---|---|---|
classpath: | classpath:com/myapp/config.xml | 從類路徑加載 |
file: | file:///data/config.xml | 以URL形式從文件係統加載 |
http: | https://myserver/logo.png | 以URL形式加載 |
(none) | /data/config.xml | 由底層的ApplicationContext實現決定 |
Table 4.1. Resource strings
4.5 ResourceLoaderAware 接口
ResourceLoaderAware 是一個特殊的標記接口,用來標記提供 ResourceLoader 引用的對象。
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader 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”.)。
最後更新:2017-05-18 11:01:57