Spring資源

1 簡介

Java的標準java.net.URL類和各類URL前綴的標準處理器不足以知足全部的低級別資源訪問。例如沒有標準化的URL實現用於訪問從類路徑上獲取的資源,或者與ServletContext相關的資源。儘管能夠爲特定的URL前綴註冊新的處理器(與已有的前綴處理器例如http:類似),這通常是很複雜的,而且URL接口仍然缺少一些須要的方法,例如檢查指向的資源是否存在的方法。java

2 Resource接口

Spring的Resource接口是一個更強大的接口,用於對低級資源的抽象訪問。web

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():返回一個布爾值指示這個資源是否以物理形式存在;
  • isOpen():返回一個布爾值指示這個資源是否表示具備打開流的句柄。若是爲true,InputStream不能被多重讀取,而且必須被讀取一次而後關閉來避免資源泄露。對於全部一般的資源實現,除了InputStreamResource以外,它是false。
  • getDescription():返回資源的描述,用於使用資源時的錯誤輸出。它常常是全限定的文件名活着資源的實際URL。

其它方法容許你獲取一個表明資源的實際URL或文件對象(若是底層實現兼容而且在功能上支持)。數組

Resource抽象在Spring自身被普遍的使用,在許多方法簽名中當須要一個資源時它做爲一個參數類型。在一些Spring API的其它方法中(例如各類ApplicationContext實現的構造方法),使用一個字符串,以未裝飾或簡單的形式用於建立適合該上下文實現的Resource,或者經過字符串路徑中特定的前綴容許調用者指定必須建立和使用的特定Resource實現。服務器

在Resource接口被Spring普遍使用的同時,它實際上在用戶編寫的代碼中做爲通常的實用工具類也是很是有用的,主要用於訪問資源,甚至當代碼不知道或不關心Spring的任何其它部分時。儘管這將代碼和Spring耦合在一塊兒,它實際上僅僅耦合了一小部分工具類,做爲URL的功能更豐富的替代,能夠等價於出於這個目使用的其它任何庫。app

要注意的是,資源抽象不是取代而是包裝它。例如URLResource包裝URL,而且使用被包裝的URL完成它的工做。ide

3 內建的Resource實現

在Spring中提供了一些開箱即用的Resource實現。函數

3.1 UrlResource

UrlResource包裝了java.net.URL,用於訪問一般可經過URL訪問的任何對象,例如文件、HTTP目標、FTP目標等。全部URL有一個標準化的字符串表示方法,適當的標準化前綴用於指示URL類型。file:表示訪問文件系統路徑,http:用於經過HTTP協議訪問資源,ftp:用於經過FTP訪問資源等等。工具

UrlResource對象在Java代碼中使用構造函數顯式建立,可是常常被隱式建立,經過調用使用一個String參數表示一個路徑的API方法。對於後一種狀況,一個JavaBean PropertyEditor最終會決定建立什麼類型的Resource。若是路徑字符串中包含一些衆所周知的前綴例如classpath:,它會爲該前綴建立一個恰當的資源。若是不能識別前綴,它會假定這僅僅是一個標準URL字符串,而且建立UrlResource。佈局

3.2 ClassPathResource

這個類表明從類路徑獲取的資源。它或者使用線程上下文類加載器,一個給定的類加載器,或者使用一個用戶給定的加載資源的類。

若是類路徑資源位於文件系統,這個Resouce實現支持將其解析爲java.io.File;可是不支持位於jar文件中且還沒有解壓(經過servlet引擎或其它環境)到文件系統中的類路徑資源。爲了解決這個問題各類資源實現老是支持解析爲java.net.URL。

ClassPathResource在Java代碼中使用ClassPathResource構造函數現實建立;可是常常被隱式建立,經過調用用一個String參數表示一個路徑的API方法。對於後一種狀況,一個JavaBean PropertyEditor會識別字符串路徑的特定前綴classpath:,而後建立一個ClassPathResource。

3.3 FileSystemResource

用於處理java.io.File的Resource實現。它支持解析爲一個File或URL。

3.4 ServletContextResource

這個Resource實現用於ServletContext資源,使用相應web應用程序的根目錄來解析相關的路徑。

它支持流讀取和URL讀取,可是僅僅當web應用壓縮文件被解壓而且資源存在於文件系統時容許訪問java.io.File。壓縮文件是否被解壓到文件系統,仍是直接從JAR訪問或者從其它地方例如DB訪問,實際上依賴於Servlet容器。

3.5 InputStreamResource

用於給定的InputStream的Resource實現。只有在沒有具體的資源實現適用的狀況下才應該使用。實際上,應儘量優先使用ByteArrayResource或任何基於文件的Resource實現。與其它資源實現相反,這是一個對已經打開資源的描述符——所以isOpen()方法返回true。若是須要將資源描述符保存在某處或者須要屢次讀取流,請不要使用它。

3.6 ByteArrayResource

這個Resource實現用於一個給定的byte數組。它爲給定的數組建立一個ByteArrayInputStream。

從任何給定的byte數組讀取內容而不需使用一次性的InputStreamResource是有用的.

4 ResourceLoader

ResourceLoader被可以返回(即加載)Resource實例的對象實現。

public interface ResourceLoader {

    Resource getResource(String location);

}

全部應用上下文都實現了ResourceLoader接口,所以全部應用上下文能夠用於獲取Resource實例。

當在特定的應用上下文上調用getResource(),而且給定的位置路徑沒有特定的前綴,它會返回與具體應用上下文相關的Resource類型。例如,假定下面的代碼段有ClassPathXmlApplicationContext實例執行:

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

會返回一個ClassPathResource對象;若是一樣的方法被FileSystemXmlAPplicationContext實例執行,會返回FileSystemResource。對於WebApplicationContext,則返回ServletContextResource等等。

所以能夠以適合於特定應用程序上下文的方式加載資源。

另外一方面,也能夠經過指定classpath:前綴來強制返回ClassPathResource,而不考慮應用上下文類型:

Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");

類似的,能夠指定任何一個標準的java.net.URL前綴來強制返回UrlResource:

Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt");

下面的表格總結了String轉換爲Resource的規則:

前綴 例子 解釋
classpath: classpath:com/myapp/config.xml 從類路徑加載
file: file:///data/config.xml 做爲URL加載,從文件系統
http: http://myserver/logo.png 做爲URL加
(沒有前綴) /data/config.xml 依賴於底層的ApplicationContext

5 ResourceLoaderAware接口

ResourceLoaderAware接口是一個特殊的標記接口,識別但願獲取ResourceLoader引用的對象。

public interface ResourceLoaderAware {

    void setResourceLoader(ResourceLoader resourceLoader);
}

當一個類實現了ResourceLoaderAware接口而且被部署到應用上下文中(做爲一個Spring管理的bean),它被應用上下文識別爲ResourceLoaderAware。而後應用上下文將會調用setResourceLoader(ResourceLoader resourceLoader)方法,將自身做爲參數提供給該方法(記住,Spring中全部的應用上下文都實現了ResourceLoader接口)。

固然,因爲一個ApplicationContext是一個ResourceLoader,bean也能夠實現ApplicationContextAware接口而且使用被提供的應用上下文直接用於加載資源,可是通常狀況下,最好若是僅僅是須要加載資源使用特定的ResourceLoader接口。這樣代碼僅僅會與被認爲是工具接口的資源加載接口耦合,而不是耦合於整個Spring ApplicationContext接口。

從Spring 2.5開始可使用ResourceLoader的自動裝配做爲實現ResourceLoaderAware接口的一種替代方法。傳統的構造函數和經過類型的自動裝配模式如今能夠爲構造函數參數和setter方法參數提供ResourceLoader依賴。對於更加靈活的方式(包括自動裝配域和多個參數的方法),可使用新的基於註解的自動裝配特徵。在這種狀況下,ResourceLoader能夠被自動注入到期待類型爲ResourceLoader的域、構造函數參數或者方法參數中,只有上述域、方法或者構造函數帶有@Autowired註解。

6 資源做爲依賴

若是一個bean自身要經過某種動態決定過程決定和提供資源路徑,bean使用ResourceLoader接口加載資源是頗有意義的。做爲一例子,考慮以某種模式加載一個模版,其中所需的特定資源取決於用戶角色。若是資源是靜態的,徹底消除對ResourceLoader的使用是有意義的,只要讓bean暴露它須要的Resource屬性,而且指望它們被注入到bean中。

全部應用上下文都會註冊和做爲一個特殊JavaBean使用的PropertyEdtior將String路徑轉換爲Resource對象,它使得注入這些Resource依賴變的簡單。因此若是myBean有一個Resource類型的模版屬性,它能夠被配置爲一個簡單的字符串,以下所示:

<bean id="myBean" class="...">
    <property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>

請注意資源路徑沒有前綴,因爲應用上下文自身將會被用作ResourceLoader,資源自身被加載爲ClassPathResource、FileSystemResource或者ServletContextResource取決於具體的上下文類型。

若是須要強制指定資源的類型,可使用前綴。下面的兩個例子展現瞭如何強制使用ClassPathResource和UrlResource(後一種用於獲取文件系統中的文件):

<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>

7 應用上下文和資源路徑

7.1 構造應用上下文

一個應用上下文的構造函數(用於特定的應用上下文類型)通常使用一個字符串或字符串數組做爲資源(例如XML文件)的位置路徑,這些資源組成了上下文的定義。

當這樣的位置路徑沒有前綴時,特定的Resource類型從這個路徑構建而且用於加載bean定義,Resource類型依賴並適用於特定的應用上下文。例如,若是以下建立一個ClassPathXmlApplicationContext:

ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

bean定義將會從類路徑加載,由於使用了ClassPathResource。可是若是建立一個FileSystemXmlApplicationContext以下:

ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");

bean定義將會從文件系統位置加載,在這種狀況下相對於當前工做目錄。

請注意,在位置路徑前使用特殊的類路徑前綴或標準URL前綴將會覆蓋默認的被建立用於加載bean定義的Resource類型。因此,這個FileSystemXmlApplicationContext

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");

將會實際從類路徑加載bean定義。然而,它仍然是一個FileSystemXmlApplicationContext。若是後來被做爲ResourceLoader使用,任何無前綴的路徑將仍然會做爲文件系統路徑被處理。

構造ClassPathXmlApplicationContext實例 —— 快捷方式

ClassPathXmlApplicationContext提供了系列構造函數用於快速實例化。基本的方法是替狗一個字符串數組包含了XML文件自身的文件名(沒有路徑信息),而且也能夠提供一個Class;ClassPathXmlApplicationContext將會從被提供的類判斷路徑信息。

一個例子會使其清晰,考慮一個目錄佈局以下所示:

com/
	foo/
		services.xml
		daos.xml
		MessengerService.class

一個ClassPathXmlApplicationContext實例能夠像下面這樣被實例化,它是由在services.xml和daos.xml中的bean定義組成。

ApplicationContext ctx = new ClassPathXmlApplicationContext(
    new String[] {"services.xml", "daos.xml"}, MessengerService.class);

能夠參考ClassPathXmlApplicationContext的javadoc獲取各類構造函數的詳細信息。

7.2 應用上下文構造函數的資源路徑中的通配符

在應用上下文構造函數的參數值中的資源路徑能夠是簡單路徑(如上所示),與目標資源是一一映射的關係,或者能夠包含特殊的"classpath*:"前綴和內部的Ant風格的正則表達式(使用Spring的PathMatcher工具匹配)。後者都是有效的通配符。

這種機制的一個用途是組件樣式應用組裝。全部組件能夠發佈上下文定義片斷到一個衆所周知的位置路徑,而且當最終的應用上下文使用經過classpath*前綴的相同路徑被建立時,全部的組件片斷都會被自動打包。

_本身的補充:應用於不一樣類路徑下的相同文件,好比resource1.jar中有一個com.text.rs.jarAppcontext.xml,而resource2.jar中也有一個com.text.rs.jarAppcontext.xml。經過

ApplicationContext ctx = new ClassPathXmlApplicationContext( "classpath*:com/test/rs/jarAppcontext.xml")

能夠將兩個jar中的同名文件都加載進來。而若是寫成下面的代碼,就只能找到其中的一個xml文件(順序取決於jar包的加載順序)

ApplicationContext ctx = new ClassPathXmlApplicationContext( "classpath:com/test/rs/jarAppcontext.xml");

_

請注意,此通配符特定於在應用上下文的構造函數中的資源路徑使用(或當直接使用PathMatcher工具類層次結構時),而且在構造時被解析。與Resource類型自己沒有任何關係。不可以使用classpath*:前綴構造實際的Resource,由於一個Resource同時僅僅指定一個資源。

Ant風格模式

當路徑位置包含Ant風格模式,例如:

/WEB-INF/*-context.xml
  com/mycompany/**/applicationContext.xml
  file:C:/some/path/*-context.xml
  classpath:com/mycompany/**/applicationContext.xml

解析器遵循更復雜但定義的過程來嘗試解析通配符。它爲最後一個非通配符段生成資源並從中獲取一個URL。若是URL不是以jar:開頭或容器特定的變體(例如WebLogic中的zip:,webSphere中的wsjar等),那麼將產生一個java.io.File而且用於經過遍歷文件系統來解析通配符。在jar URL的狀況下,解析器或者獲取一個java.net.JarURLConnection或者手動解析jar URL而後遍歷jar文件的內容來解析通配符。

對可移植性的影響

若是指定的路徑是文件URL(顯式的或隱式的),因爲基本的ResourceLoader是文件系統資源加載器,通配符能夠保證以徹底可移植的方式工做。

若是指定的路徑是一個類路徑位置,那麼解析器經過調用ClassLoader.getResource()獲取最後一段非通配符路徑段的URL。由於這僅僅是路徑的一個節點(不是最後的文件),在這種狀況下對於返回什麼類型的URL其實是未定義的。在實踐中,常常是一個java.io.File對象表明目錄,類路徑資源解析爲一個文件系統位置;或某一類的jar URL,類路徑資源解析爲jar位置。儘管如此,這種操做仍然存在可移植性問題。

若是從最後一個非通配符段獲取到一個jar URL,解析器必須可以從它獲取java.net.JarURLConnetcion,或者手動解析jar URL,可以遍歷jar的內容,並解析通配符。這在大多數環境中工做良好,可是在一些其它環境會失效;強烈建議jar的通配符資源解析在特定的環境中被全面測試。

Classpath*: 可移植的classpath*前綴

當構建一個基於XML的應用上下文,一個位置字符串可使用特殊的classpath*: 前綴:

ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");

這個特定的前綴指定全部匹配給定名稱的類路徑資源必須被獲取(在內部,這實際上經過ClassLoader.getResources(...)方法調用完成),而後合併造成最終的應用上下文定義。

通配符的類路徑依賴於底層類加載器的getReosources()方法。因爲現今大多數應用服務器都提供它們本身的類加載器實現,處理jar文件的行爲也許會不一樣。用於測試classpath*是否工做的一個簡單方法是使用類加載器從類路徑下的jar加載文件:getClass().getClassLoader().getResources("<someFileInsideTheJar>")。使用相同名字可是被放置於不一樣位置的文件測試。若是返回了不正確的結果,查看應用服務器的文檔找到影響類加載器行爲的設置。

classpath*: 前綴也能夠與位置路徑其他部分中的PathMatcher模式組合使用,例如classpath*:META-INF/*-beans.xml。在這種狀況下解析策略很簡單:ClassLoader.getResources()調用被用於從最後一個非通配符路徑段獲取全部在類加載器層次結構中匹配的資源,而後上文所述的PathMatcher解析策略用於子路徑的通配符資源解析。

有關通配符的其它說明

請注意,classpath*:與Ant風格模式在模式開始前至少一個根目錄時有效,除非實際的目標文件位於文件系統。這意味着像classpath*:*.xml這樣的模式不能從jar文件中檢索文件,而只能從擴展目錄的根目錄中檢索文件。這是由JDK的ClassLoader.getResources()方法的侷限性形成的,當傳入空字符串時它僅僅返回文件系統位置(代表要搜索的根目錄)。

classpath:資源中的Ant風格模式若是要搜索的根包衛浴多個類路徑位置不保證可以找到匹配的資源。由於以下的一個資源:

com/mycompany/package1/service-context.xml

也許位於一個路徑,可是當路徑以下:

classpath:com/mycompany/**/service-context.xml

被解析時,解析器將會使用getResource("com/mycompany")返回的(第一個)URL工做。若是這個基礎包節點在多個類加載器位置中存在,實際的資源可能未在其中。所以,在這種狀況下優先使用"classpath*:"和相同的Ant風格模式,它會搜索包含根包的全部類路徑位置。

7.3 文件系統資源注意事項

未附加到FileSystemApplicationContext的FileSystemResource(即FileSystemApplicationContext並非實際的ResourceLoader),FileSystemResource將按照指望來處理絕對路徑和相對路徑。相對路徑與當前工做路徑相關,而絕對路徑與文件系統的根目錄相關。

因爲向後的兼容系的緣由,當FileSystemApplicationContext是一個ResourceLoader時它將改變。FileSystemApplicationContext簡單的強制全部的FileSystemResource實例做爲相對路徑處理,不論它們是否以斜槓開頭。在實踐中,這意味下面的寫法等價:

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("conf/context.xml");
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("/conf/context.xml");

下面的寫法也等價:(即便他們是不一樣的,由於一個是相對的,另外一個是絕對的。)

FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");

在實踐中,若是須要絕對文件系統路徑,最好放棄將FileSystemResource/FileSystemXmlApplicationContext與絕對路徑一塊兒使用,使用帶file: URL前綴的URLResource便可。

// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
    new FileSystemXmlApplicationContext("file:///conf/context.xml");
相關文章
相關標籤/搜索