關於spring Resource的資源類型以及繼承體系我們已經在上一篇文件粗略的說了一下。Spring加載Resource文件是通過ResourceLoader來進行的,那么我們就先來看看ResourceLoader的繼承體系,讓我們對這個模塊有一個比較系統的認知。
上圖僅右邊的繼承體系,僅畫至AbstractApplicationContext,由於ApplicationContext的繼承體系,我們已經在前面章節給出,所以為了避免不必要的復雜性,本章繼承體系就不引入ApplicationContext。
我們還是來關注本章的重點————classpath 與 classpath*以及通配符是怎么處理的
首先,我們來看下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*開頭,但路徑不包含通配符的
- protected Resource[] findAllClassPathResources(String location) throws IOException {
- String path = location;
- if (path.startsWith("/")) {
- path = path.substring(1);
- }
- Enumeration<URL> resourceUrls = getClassLoader().getResources(path);
- Set<Resource> result = new LinkedHashSet<Resource>(16);
- while (resourceUrls.hasMoreElements()) {
- URL url = resourceUrls.nextElement();
- result.add(convertClassLoaderURL(url));
- }
- return result.toArray(new Resource[result.size()]);
- }
我們可以看到,最關鍵的一句代碼是:Enumeration<URL> resourceUrls = getClassLoader().getResources(path);
- public ClassLoader getClassLoader() {
- return getResourceLoader().getClassLoader();
- }
- public ResourceLoader getResourceLoader() {
- return this.resourceLoader;
- }
- //默認情況下
- public PathMatchingResourcePatternResolver() {
- this.resourceLoader = new DefaultResourceLoader();
- }
- public Enumeration<URL> getResources(String name) throws IOException {
- Enumeration[] tmp = new Enumeration[2];
- if (parent != null) {
- tmp[0] = parent.getResources(name);
- } else {
- tmp[0] = getBootstrapResources(name);
- }
- tmp[1] = findResources(name);
- return new CompoundEnumeration(tmp);
- }
- 不以classpath*開頭,且路徑不包含通配符的
- 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開頭,則創建為一個ClassPathResource,否則則試圖以URL的方式加載資源,創建一個UrlResource.
- 路徑包含通配符的
- 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()]);
- }
- protected String determineRootDir(String location) {
- int prefixEnd = location.indexOf(":") + 1;
- int rootDirEnd = location.length();
- while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
- rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
- }
- if (rootDirEnd == 0) {
- rootDirEnd = prefixEnd;
- }
- return location.substring(0, rootDirEnd);
- }
