classpath 與 classpath*以及通配符是怎么處理的
Spring加載Resource文件是通過ResourceLoader來進行的,那么我們就先來看看ResourceLoader的繼承體系,讓我們對這個模塊有一個比較系統的認知。

首先,我們來看下ResourceLoader的源碼
public interface ResourceLoader { /** Pseudo URL prefix for loading from the class path: "classpath:" */ String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; Resource getResource(String location); ClassLoader getClassLoader(); }
我們發現,其實ResourceLoader接口只提供了classpath前綴的支持。而classpath*的前綴支持是在它的子接口ResourcePatternResolver中。
public interface ResourcePatternResolver extends ResourceLoader { /** * Pseudo URL prefix for all matching resources from the class path: "classpath*:" * This differs from ResourceLoader's classpath URL prefix in that it * retrieves all matching resources for a given name (e.g. "/beans.xml"), * for example in the root of all deployed JAR files. * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX */ String CLASSPATH_ALL_URL_PREFIX = "classpath*:"; Resource[] getResources(String locationPattern) throws IOException; }
通過2個接口的源碼對比,我們發現ResourceLoader提供 classpath下單資源文件的載入,而ResourcePatternResolver提供了多資源文件的載入。
ResourcePatternResolver有一個實現類:PathMatchingResourcePatternResolver,那我們直奔主題,查看PathMatchingResourcePatternResolver的getResources()
public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); //是否以classpath*開頭 if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { //是否包含?或者* 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)}; } } }
由此我們可以看出在加載配置文件時,以是否是以classpath*開頭分為2大類處理場景,每大類在又根據路徑中是否包括通配符分為2小類進行處理,
處理的流程圖如下:

其實很簡單
- 如果以classpath開頭,則創建為一個ClassPathResource,否則則試圖以URL的方式加載資源,創建一個UrlResource.
- 如果路徑包含通配符的, 這種情況是最復雜的,涉及到層層遞歸,那我把加了注釋的代碼發出來大家看一下,其實主要的思想就是
1.先獲取目錄,加載目錄里面的所有資源
2.在所有資源里面進行查找匹配,找出我們需要的資源
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException { //拿到能確定的目錄,即拿到不包括通配符的能確定的路徑 比如classpath*:/aaa/bbb/spring-*.xml 則返回classpath*:/aaa/bbb/ //如果是classpath*:/aaa/*/spring-*.xml,則返回 classpath*:/aaa/ String rootDirPath = determineRootDir(locationPattern); //得到spring-*.xml String subPattern = locationPattern.substring(rootDirPath.length()); //遞歸加載所有的根目錄資源,要注意的是遞歸的時候又得考慮classpath,與classpath*的情況,而且還得考慮根路徑中是否又包含通配符,參考上面那張流程圖 Resource[] rootDirResources = getResources(rootDirPath); Set<Resource> result = new LinkedHashSet<Resource>(16); //將根目錄所有資源中所有匹配我們需要的資源(如spring-*)加載result中 for (Resource rootDirResource : rootDirResources) { rootDirResource = resolveRootDirResource(rootDirResource); if (isJarResource(rootDirResource)) { result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern)); } else if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher())); } else { result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern)); } } if (logger.isDebugEnabled()) { logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result); } return result.toArray(new Resource[result.size()]); }
好了,說了這么多,看下面的例子:
classpath:spring-servlet.xml
說明:無通配符,必須完全匹配
classpath:spring-servlet?.xml
說明:匹配一個字符,例如 spring-servlet1.xml 、 spring-servlet2.xml
classpath:config/*/spring-servlet.xml
說明:匹配零個或多個字符串(只針對名稱,不匹配目錄分隔符等),例如:config/a/
spring-servlet.xml 、 user/b/
spring-servlet.xml ,但是不匹配 user/
spring-servlet.xml
classpath:config/**/spring-servlet.xml
說明:匹配路徑中的零個或多個目錄,例如:
config/a/ab/abc/
spring-servlet.xml,同時也能匹配
config/
spring-servlet.xml
classpath:**/*.xml
說明:表示在所有的類路徑中查找和加載文件名以“.xml”結尾的配置文件,但重復的文件名只加載其中一個,視加載順序決定
classpath*:config/**/*.xml
classpath*:**/*.xml
說明:“classpath*:”表示加載多個資源文件,即使重名也會被加載,j包括jar包里的重復的xml文件。
