1. 程式人生 > >FileSystemXmlApplicationContext路徑格式及解析方式

FileSystemXmlApplicationContext路徑格式及解析方式

瞭解完了FileSystemXmlApplicationContext建構函式,我們來看看路徑解析的問題。

  • 支援路徑格式的研究。(絕對?相對?萬用字元?classpath格式又如何?
  • 路徑如何解析?

下面,我們就來一一驗證和解答。先放出本次測試用的配置檔案(app-context.xmltest.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生命週期的開始,我們就以它為入口,順藤摸瓜,時序圖如下

最終,我們找到解析路徑的關鍵方法,PathMatchingResourcePatternResolvergetResources方法和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確認,確實走入了相應的分支,分別構造了UrlResourceClassPathResource例項。所以,之後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*開頭的又是如何呢?我們下回分解……