FileSystemXmlApplicationContext路徑格式及解析方式
瞭解完了FileSystemXmlApplicationContext建構函式,我們來看看路徑解析的問題。
- 支援路徑格式的研究。(絕對?相對?萬用字元?classpath格式又如何?
- 路徑如何解析?
下面,我們就來一一驗證和解答。先放出本次測試用的配置檔案(app-context.xml和test.properties):
<bean id="placeHolderConfig"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="locations">
<list>
<value>classpath*:spring/test.properties</value>
</list>
</property>
</bean>
<bean id="veryCommonBean" class="kubi.coder.bean.VeryCommonBean" >
<property name="name" value="${test.name}"></property>
</bean>
test.properties
test.name=verycommonbean-name
首先想到的自然是最普通的絕對路徑:
/**
* 測試通過普通的絕對路徑:
* <p>D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml</p>
* 讀取配置檔案
*
* @author lihzh
* @date 2012-5-5 上午10:53:53
*/
@Test
public void testPlainAbsolutePath() {
String path = "D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml";
ApplicationContext appContext = new FileSystemXmlApplicationContext(path);
assertNotNull(appContext);
VeryCommonBean bean = appContext.getBean(VeryCommonBean.class);
assertNotNull(bean);
assertEquals("verycommonbean-name", bean.getName());
}
測試通過,我們來看下Spring是怎麼找到該檔案的。之前已經說過refresh這個函式,是Spring生命週期的開始,我們就以它為入口,順藤摸瓜,時序圖如下
最終,我們找到解析路徑的關鍵方法,PathMatchingResourcePatternResolver的getResources方法和DefaultResourceLoader中的getResource方法:
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Only look for a pattern after a prefix here
// (to not get fooled by a pattern symbol in a strange prefix).
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
其中常量
CLASSPATH_ALL_URL_PREFIX = "classpath*:";
CLASSPATH_URL_PREFIX = "classpath:";
我們輸入的路徑是絕對路徑:”D:\workspace-home\spring-custom\src\main\resources\spring\app-context.xml”。不是以classpath*開頭的,所以會落入else之中。在else中:getPathMatcher().isPattern(),實際是呼叫AntPathMatcher中的isPattern()方法:
public boolean isPattern(String path) {
return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
}
是用來判斷”:”以後的路徑中是否包含萬用字元”*“或者 “?”。我們的路徑顯然也不包含,所以最終會直接走入getResource方法。
路徑仍然既不是以classpath開頭的,也不是URL格式的路徑,所以最終會落入getResourceByPath(location)這個分支,而我們之前介紹過,這個方法恰好是在FileSystemXmlApplicationContext這個類中複寫過的
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
我們給的路徑不是以”/”開頭,所以直接構造了一個FileSystemResource
public FileSystemResource(String path) {
Assert.notNull(path, "Path must not be null");
this.file = new File(path);
this.path = StringUtils.cleanPath(path);
}
即用路徑直接構造了一個File。這裡StringUtil.cleanPath方法主要是將傳入的路徑規範化,比如將windows的路徑分隔”\“替換為標準的”/”,如果路徑中含有”.”(當前資料夾),或者”..”(上層資料夾),則計算出其真實路徑。而File本身是支援這樣的路徑的,也就是說,spring可以支援這樣的路徑。出於好奇,我們也針對這個方法測試如下:
/**
* 測試通過含有.或者..的絕對路徑
* <p>D:\\workspace-home\\spring-custom\\.\\src\\main\\resources\\spring\\..\\spring\\app-context.xml</p>
* 讀取配置檔案
*
* @author lihzh
* @date 2012-5-5 上午10:53:53
*/
@Test
public void testContainDotAbsolutePath() {
String path = "D:\\workspace-home\\spring-custom\\.\\src\\main\\resources\\spring\\..\\spring\\app-context.xml";
ApplicationContext appContext = new FileSystemXmlApplicationContext(path);
assertNotNull(appContext);
VeryCommonBean bean = appContext.getBean(VeryCommonBean.class);
assertNotNull(bean);
assertEquals("verycommonbean-name", bean.getName());
}
容器可以正常初始化。路徑計算正確。
補充說明:Spring最終讀取配置檔案,是通過InputStream載入的,Spring中的各種Resource的最上層介面InputStreamResource中定義了唯一的一個方法getInputStream。也就是說,只要保證各Resource的實現類的getInputStream方法能夠正常獲取流,Spring容器即可解析初始化。
對於FileSystemResource而言,其實現如下:
/**
* This implementation opens a FileInputStream for the underlying file.
* @see java.io.FileInputStream
*/
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
}
所以,我們說,此時只有是File正常支援的格式,Spring才能正常初始化。
繼續回到前面的話題。我們目前只驗證else分支中的catch分支。根據程式碼分析,即使是FileSystemXmlApplicationContext也可以支援Classpath格式的路徑和URL格式的路徑的。驗證如下
/**
* 測試通過含有.或者..的絕對路徑
* <p>file:/D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml</p>
* 讀取配置檔案
*
* @author lihzh
* @date 2012-5-5 上午10:53:53
*/
@Test
public void testURLAbsolutePath() {
String path = "file:/D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml";
ApplicationContext appContext = new FileSystemXmlApplicationContext(path);
assertNotNull(appContext);
VeryCommonBean bean = appContext.getBean(VeryCommonBean.class);
assertNotNull(bean);
assertEquals("verycommonbean-name", bean.getName());
}
/**
* 測試通過Classpath型別的路徑
* <p>classpath:spring/app-context.xml</p>
* 通過讀取配置檔案
*
* @author lihzh
* @date 2012-5-5 上午10:53:53
*/
@Test
public void testClassPathStylePath() {
String path = "classpath:spring/app-context.xml";
ApplicationContext appContext = new FileSystemXmlApplicationContext(path);
assertNotNull(appContext);
VeryCommonBean bean = appContext.getBean(VeryCommonBean.class);
assertNotNull(bean);
assertEquals("verycommonbean-name", bean.getName());
}
驗證通過,並且通過debug確認,確實走入了相應的分支,分別構造了UrlResource和ClassPathResource例項。所以,之後Spring會分別呼叫這個兩個Resource中的getInputStream方法獲取流,解析配置檔案。附上這兩個類中的getInputStream方法,有興趣的可以繼續研究。
/**
* This implementation opens an InputStream for the given URL.
* It sets the "UseCaches" flag to <code>false</code>,
* mainly to avoid jar file locking on Windows.
* @see java.net.URL#openConnection()
* @see java.net.URLConnection#setUseCaches(boolean)
* @see java.net.URLConnection#getInputStream()
*/
public InputStream getInputStream() throws IOException {
URLConnection con = this.url.openConnection();
ResourceUtils.useCachesIfNecessary(con);
try {
return con.getInputStream();
}
catch (IOException ex) {
// Close the HTTP connection (if applicable).
if (con instanceof HttpURLConnection) {
((HttpURLConnection) con).disconnect();
}
throw ex;
}
}
/**
* This implementation opens an InputStream for the given class path resource.
* @see java.lang.ClassLoader#getResourceAsStream(String)
* @see java.lang.Class#getResourceAsStream(String)
*/
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else {
is = this.classLoader.getResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(
getDescription() + " cannot be opened because it does not exist");
}
return is;
}
上述兩個實現所屬的類,我想應該一目瞭然吧~~
至此,我們算是分析驗證通過了一個小分支下的支援的路徑的情況,其實,這只是這些都是最簡單直接的。回想剛才的分析,如果路徑包含萬用字元(?,*)spring是怎麼處理的?如果是以classpath*開頭的又是如何呢?我們下回分解……