Spring官方文檔翻譯——5.資源

5. 資源


5.1 簡介


使用標準的java.net.URL類和各種URL前綴處理器並不能滿足我們對訪問底層資源。比如,沒有用於訪問classpath下或是相對於ServletContext的資源的標準化URL實現類。儘管可以爲特定的URL前綴註冊新的處理器(類似現有對http:前綴的處理器),但是這通常比較複雜,而且URL接口仍然缺少一些有用的方法,比如可以檢查資源是否存在的方法。

5.2 Resource接口

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():返回當前代表的底層資源是否存在

isOpen():如果爲true,表示InputStream不能被多次讀取,只能讀取一次然後關閉以免造成資源泄露。對大多數資源實現類都返回false,除了InputStreamResource。

getDescription():返回當前Resource代表的底層資源的描述符,在使用該資源時輸出錯誤時使用。通常是全限定類名或是資源實際的URL。

其餘方法可以讓你獲取一個真實的且能夠代表這個資源的URL或File對象(如果底層實現兼容並且支持這個方法)。

Resource的抽象廣泛地應用於Spring自身:當需要一個資源時,在很多方法簽名中Resource會作爲其中一個參數。另外一些Spring API的方法(比如各種ApplicationContext實現類的構造器),使用一個簡單的字符串來創建一個適合這個上下文的資源,或是根據字符串中特殊的前綴來指定Resource的類型。

由於Resource接口在Spring中廣泛的被使用。也許你並不在乎Spring的其他部分,但是你可以將它(Resource)作爲一個通用的工具類應用於你的代碼來訪問資源。這僅僅只會耦合一小部分,並且這一些部分將能完全取代URL並提供額外的功能,你完全可以將它當成其他任何一個第三方的lib。

很重要的一點是:Resource抽象並沒有替換原來的功能,而是在原有的基礎上封裝了一層。例如,UrlResource封裝了一個URL,然後委託URL來工作。


5.3 內置的 Resource實現

Spring已經內置了一些Resource的實現類可供你使用:


UrlResource

UrlResource封裝了一個java.net.URL,可以用來訪問任何能夠用URL訪問的對象,比如文件,HTTP目標,FTP目標等等。所有這些URL都有一個標準化的字符串表示,從而可以用前綴就來表明URL的類型。file:用來訪問系統路徑,http:通過HTTP協議來訪問資源,ftp:通過FTP來訪問資源等等。

UrlResource對象可以使用構造器來創建,但是通常是使用一個表示路徑的字符串來創建。對於後一種情況,PropertyEditor將會最終決定創建哪種資源。如果表示路徑的字符串中包含一些出名的前綴比如:classpath:,那麼將會創建一個符合這個前綴的資源。然而,如果沒有發現前綴,那麼它將會視其爲一個標準的URL字符串,然後創建一個UrlResource。


ClassPathResource

這個類表示從classpath下獲取的資源。它會使用當前線程上下文類加載器,指定的類加載器,或是指定的類來加載資源。

如果classpath資源存在於文件系統(不包括打包在jar的情況),那麼ClassPathResource同樣支持用java.io.File來解決。另外,多種Resource實現總是支持用java.net.URL來解決(當然啦,這纔是底層實現嘛,只不過是方便和麻煩的關係咯)。

ClassPathResource對象可以使用構造器來創建,但是通常是使用一個表示路徑的字符串來創建。對於後一種情況,PropertyEditor將會從中發現classpath:前綴,然後創建一個ClassPathResource。


FileSystemResource

這個Resource實現是爲了處理java.io.File,很顯然,它也支持用File或URL來解決。


ServletContextResource

這是對應ServletContext資源的實現類,能夠解析相對於web應用根目錄的路徑。

這個類支持流的訪問以及URL訪問,但僅當web應用被解壓縮時才能使用java.io.File來訪問。無論web應用是否被解壓,或是直接訪問JAR或是別的地方(比如DB)實際上取決於Servlet容器。


InputStreamResource

對於一個指定的InputStream的Resource實現類。只有當沒有明確的Resource實現可以使用時纔會使用它。儘可能優先使用ByteArrayResource或是其餘基於文件的Resource實現類。


ByteArrayResource

對應一個指定的字符數組的Resource實現類。它爲指定的字符數組創建了一個ByteArrayInputStream。

當從一個指定的字符數組載入內容時,這個類很有用。因爲這樣就無需去使用一次性的InputStreamResource了。


5.4 ResourceLoader

可以通過實現ResourceLoader接口來返回(也就是加載)Resource實例。

public interface ResourceLoader {
	Resource getResource(String location);
}

所有應用上下文都實現了ResourceLoader接口,因此所有的應用上下文都可以用來取得資源實例。

當你在特定的應用上下文中調用getResource()方法時,若此時資源路徑沒有指定前綴,那麼你將會得到一個適合當前應用上下文的資源。比如,假設下面代碼片段中的ctx是ClassPathXmlApplicationContext:

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

那麼返回的將會是一個ClassPathResource。而如果同樣的方法在FileSystemXmlApplicationContext中調用,那麼你將得到一個FIleSystemResource。如果在WebApplicationContext中,那麼你將得到一個ServletContextResource。

如此依賴,你可以根據應用上下文來加載資源。

而另一方面,你也可以不管應用上下文的類型來強制加載ClassPathResource,只需要通過指定前綴"classpath:"即可:

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轉化的策略(此處略)


5.5 ResourceLoaderAware接口

ResourceLoaderAware接口是一個特殊的標記接口,標記那些希望得到ResourceLoader引用的對象。

public interface ResourceLoaderAware {
	void setResourceLoader(ResourceLoader resourceLoader);
}

當一個類實現了ResourceLoaderAware接口並且被部署到了應用上下文中(比如一個Spring管理的bean),它將被應用上下文視爲ResourceLoaderAware。然後應用上下文會調用它的setResourceLoader(ResourceLoader)方法,並會提供自身作爲參數(記住,Spring中所有的應用上下文都實現了ResourceLoader接口)。

當然,由於一個ApplicationContext是一個ResourceLoader,bean同樣可以實現ApplicationContextAware接口來加載資源,但是總的來說,如果只需要資源加載這麼一個功能的話,最好使用細化了的ResourceLoader接口。因爲這樣的話,代碼只會與資源加載的接口耦合,這個接口可以當做一個工具接口來使用,而不用與整個Spring的ApplicationContext耦合。

在Spring 2.5中,你可以利用自動裝配而無需實現ResourceLoaderAware接口。“傳統的”constructor和byType自動裝配模式都能夠提供ResourceLoader的引用。要想更加靈活(包括自動裝配字段和多個參數方法),可以使用基於註解的自動裝配。在這種情況下,ResourceLoader將會被自動裝配到帶有@Autowired註解的字段,構造器參數或是方法參數。


5.6 Resources作爲依賴(注入Resources)

如果bean自身將要通過某種動態的方式來決定和提供資源路徑,那麼它應該使用ResourceLoader接口來加載資源。比如需要根據用戶的角色來加載某種模板。如果資源是固定的,就沒有必要使用ResourceLoader接口,只需要讓bean暴露Resource屬性,然後注入即可。

所有的應用上下文註冊並使用一個特殊的PropertyEditor,它可以將字符串路徑轉換成Resource對象,這就將注入Resource屬性變得簡單了。那麼比如myBean有一個Resource類型的template屬性,它可以通過一個簡單的字符串來注入這個屬性,比如:

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


注意這個資源路徑沒有前綴,而且由於這個應用上下文將被當成ResourceLoader,那麼將會根據當前的應用上下文類型來決定被載入的資源是ClassPathResource,FileSystemResource或是ServletContextResource。

可以通過指定前綴來強制使用指定的Resource類型。下面兩個例子顯示瞭如果強制使用ClassPathResource和UrlResource。

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


5.7 應用上下文和Resource路徑

構造應用上下文

一個應用上下文的構造器(對一個指定的應用上下文)使用一個字符串或者字符串數組來定位資源,比如構造BeanDefinition的XML文件的路徑。

當這樣的一個路徑沒有前綴時,那麼將會根據當前應用上下文的類型來決定資源的類型。例如,如果你用如下方式創建一個ClassPathXmlApplicationContext:

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

應用上下文將會從classpath下載入bean定義,並被當成一個ClassPathResource。但是如果你以如下的方式創建一個FileSystemXmlApplicationContext:

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


此時bean定義將會從文件系統的某個位置被載入,相對於當前工作目錄的路徑。

注意,如果在路徑字符串上帶有特殊的classpath前綴或是標準的URL前綴的話,那麼這將覆蓋默認的Resource類型。所以下面的FileSystemXmlApplicationContext實際上將會從classpath來載入bean定義。不過它仍舊還是一個FileSystemXmlApplicationContext。如果它後來又被當做一個ResourceLoader來使用,那麼任何不帶前綴的路徑仍然被看做一個文件系統的路徑(注:這裏我需要測試一下ClassPathXmlApplicationContext等用file前綴是否生效)。

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

創建ClassPathXmlApplicationContext實例——快捷方式

ClassPathXmlApplicationContext暴露了許多構造器用來爲實例化提供方便。最基本的是一個字符串數組參數提供XML文件的文件名(不要路徑),另一個參數提供一個Class對象。那麼ClassPathXmlApplicationContext將會從提供的Class對象來得到路徑。

希望下面的例子可以讓你看的更清楚。考慮下面的目錄結構:

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

一個ClassPathXmlApplicationContext實例可以被如此實例化:

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

其中services.xml、daos.xml中包含了一些的bean定義。


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

應用上下文構造方法中的資源路徑參數可能是一個簡單的路徑(就像上面所示),它和目標資源是一一對應的,或者是包含一個特殊的“classpath*:”前綴和/或內部的Ant風格的正則表達式(使用Spring的PathMatcher工具來匹配)。後兩者都是有效的通配符。

這個機制的一個用途是當使用多模塊風格的應用裝配。所有的模塊都可以發佈自己的上下文定義片段到一個指定的路徑。當最終的應用上下文使用帶有前綴“classpath:*”的路徑被創建時,所有的組件片段將被自動組合起來。

注意這個通配符要針對應用上下文的構造器(或當直接使用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:”URL或容器相關的變量(比如WebLogic中的“zip:”,WebSphere中的“wsjar”),那麼將能從中得到一個java.io.File,並且會用這個File來遍歷文件系統。在jar URL的情況下,解析器會從中得到一個java.net.JarURLConnection或是手動地解析jar URL然後遍歷jar文件的內容來解析這個通配符。

對移植性的影響

如果指定的路徑是一個文件URL(無論顯式還是隱式,因爲基礎的ResourceLoader是一個文件系統的ResourceLoader),所以這種情況下通配符是可以跨平臺工作的。

如果指定的路徑是一個classpath位置,那麼解析器必須通過調用Classloader.getResource()得到路徑的非通配部分的URL。由於這僅僅是路徑的一部分(不是文件),所以在這種情況下根本沒明確定義會返回何種URL。在實踐中,總是返回一個代表目錄的java.io.File對象。。。

如果從最後一位非通配的部分得到的是一個jar URL,那麼解析器必須能夠從中得到一個java.net.JarURLConnection或是手動解析這個jar URL來解析出通配符。這在大多數的環境中都適用,但是某些情況下可能不起作用。關於jar的通配符,強烈建議在你將要使用的具體環境中做個徹底地測試。


classpath*:前綴

當構造一個基於XML的應用上下文,一個路徑字符串可能會使用特殊的"classpath*:"前綴:

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

這個特殊的前綴表明了classpath下所有匹配文件名稱的資源都會被獲取(這本質上通過一個ClassLoader.getResources(...)調用),然後將獲取到的資源組裝成最終的應用上下文定義。


classpath*:的可移植性

"classpath*:"依賴於底層classloader的getResources()方法。由於大多數應用服務器現在都提供它們自己的classloader實現,而它們的行爲可能不同,尤其當處理jar文件時。一個簡單的測試可以判斷classpath*是否有效:使用classloader來載入classpath下的jar中的一個文件——getClass().getClassLoader().getResources("<someFileInsideTheJar>")。然後將兩個同名的文件放在不同的位置來進行如上測試。如果返回了不同的結果,那麼就需要查看應用服務器的文檔來找到可能會影響classloader行爲的設置。

在位置路徑的其餘部分,classpath:*前綴也能夠和一個PathMatcher組合,比如"classpath*:META-INF/*-beans.xml"。在這種情況下,解決的策略相當簡單:先得到最靠前的付無通配位置路徑segment,然後調用ClassLoader.getResources(segment)來得到所有匹配的資源,然後將 PathMacher 的策略應用於每一個獲得的資源。


另一些關於通配符的說明

請注意,當“classpath*:”與Ant風格表達式一起使用時,需要在表達式開始前至少有一個根目錄,這樣才能生效,除非目標文件在文件系統上。這意味着“classpath*:*.xml”這樣的表達式將不會從根目錄的jar文件檢索,而只會從根目錄的擴展目錄檢索。這要追溯到JDK中ClassLoader.getResources()方法的侷限性,當傳入一個空字符串時,這個方法返回文件系統位置(也就是潛在的搜索根路徑)。

如果在多個類路徑上存在所搜索的根包,那麼Ant風格的表達式和"classpath:"前綴一起使用時,並不保證能找到匹配的底層資源。這是因爲一個底層資源:

com/mycompany/package1/service-context.xml

可能只存在於一個地方,但是當一個路徑如下所示時:

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

被解析時,解析器只會對(第一個)通過getResource("com/mycompany")返回的URL進行遍歷。如果基礎包節點"com.mycompany"存在於多個classloader位置,那麼實際的資源可能會在後面,也就說不是第一個,那麼解析器就不一定能找到資源了。因此,處理上面情況更好的方式是,使用"classpath*:"來和相同的Ant風格表達式一起工作,它將會搜索所有包含基礎包節點的類路徑。


FileSystemResource注意事項

一個不由FileSystemApplicationContext載入的FileSystemResource(這就是說,FileSystemApplicationContext不是真正的ResourceLoader)對待絕對路徑和相對路徑和正常情況一樣。相對路徑是相對於當前工作目錄,而絕對路徑則是相對於文件系統的根目錄。

然而爲了向前兼容(歷史的)原因,當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一起使用,而是直接使用UrlResource,並指定file:前綴

// actual context type doesn't matter, the Resourcewill 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");




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章