Spring源码之ResourceLoader(二):PathMatchingResourcePatternResolver实现getResources加载多文件

Spring源码之ResourceLoader(二):PathMatchingResourcePatternResolver实现getResources加载多文件Spring源码之ResourceLoader一:PathMatchingResourcePatternResolver实现getResources加载多文件findAllClassPathResources走向findPathMatchingResources走向单文件加载的getResource走向上一篇文章Spring源码之ResourceLoader(一):实现类DefaultResourceLoader实现getResource写了Spring容器加载多文件委托给了PathMatchingRes

Spring源码之ResourceLoader二:PathMatchingResourcePatternResolver实现getResources加载多文件

上一篇文章
Spring源码之ResourceLoader(一):实现类DefaultResourceLoader实现getResource写了Spring容器加载多文件委托给了PathMatchingResourcePatternResolver,

PathMatchingResourcePatternResolver针对多文件的情况有三种走向分别是:

findPathMatchingResources()

findAllClassPathResources()

getResourceLoader().getResource

下面我们来分析其源码实现。

废话不多说上源码:

@Override
public Resource[] getResources(String locationPattern) throws IOException { 
   
	Assert.notNull(locationPattern, "Location pattern must not be null"); //断言,路径不能传空
	if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { 
     //CLASSPATH_ALL_URL_PREFIX:"classpath*:",判断是否是classpath*:开头
		//getPathMatcher()就是我们AntPathMatch。isPattern就是判断这个字符串里有没有*,?,{和}这些通配符
		if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { 
   
			//带有通配符走这里
			return findPathMatchingResources(locationPattern);
		}
		else { 
   
			//没有通配符走这里,也就是直接通过路径就能找到,不用去匹配通配符
			return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));//将classpath*:去掉
		}
	}
	else { 
   //如果不是以"classpath*:"开头,就会走下面这个
		//可能是打成war包,将这个前缀拿出来。留下":"后面的
		int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
				locationPattern.indexOf(':') + 1);
		//把前缀去掉后再进行一次判断是不是isPattern,含有通配符,然后调用findPathMatchingResources
		if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { 
   
			return findPathMatchingResources(locationPattern);
		}
		else { 
   //如果不是classpath*:开头,又不是打成war包,就说明是单个文件了,就交给getResourceLoader().getResource,其实也就是交给DefaultResourceLoader
			return new Resource[] { 
   getResourceLoader().getResource(locationPattern)};
		}
	}
}

首先,判断是不是以”classpath*:”开头,这种开头就说明会匹配多个资源文件,如果是,就下面操作

if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) { 
   
//getPathMatcher()就是我们AntPathMatch。isPattern就是判断这个字符串里有没有*,?,{和}这些通配符
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { 
   
	//带有通配符走这里
	return findPathMatchingResources(locationPattern);
}
else { 
   
	//没有通配符走这里,也就是直接通过路径就能找到,不用去匹配通配符
	return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));//将classpath*:去掉
}
}

可以看到,将”classpath*:”截取掉,然后判断剩下的是不是一个Pattern模式串,也就是是否含有*,?,{和}这些通配符。getPathMatcher()返回的是this.pathMatcher,而this.pathMatcher其实就是我们前面系列文章Spring源码之AntPathMatcher(一):doMatch算法中讲的AntPathMatcher。

public PathMatcher getPathMatcher() { 
   
	return this.pathMatcher;
}
private PathMatcher pathMatcher = new AntPathMatcher();

进入getPathMatcher().isPattern()可以看到,就是判断剩下的字符串中是否含有*,?,{和}这些通配符:

public boolean isPattern(@Nullable String path) { 
   
    if (path == null) { 
   
        return false;
    } else { 
   
        boolean uriVar = false;

        for(int i = 0; i < path.length(); ++i) { 
   
            char c = path.charAt(i);
            if (c == '*' || c == '?') { 
   
                return true;
            }

            if (c == '{') { 
   
                uriVar = true;
            } else if (c == '}' && uriVar) { 
   
                return true;
            }
        }

        return false;
    }
}

如果含有通配符,说明是一个Pattern模式串,会去匹配所有符合模式串的路径资源。比如”classpath*: *.properties”会匹配加载所有classpath下的properties文件。这种情况下,调用findPathMatchingResources(locationPattern)处理。也就是findPathMatchingResources走向,如果剩下的字符串中没有通配符,也就是直接通过路径就能找到,那我们就走到findAllClassPathResources方法,这里我们先说下没有通配符的情况,也就是findAllClassPathResources走向

findAllClassPathResources走向

//没有通配符走这里,也就是直接通过路径就能找到,不用去匹配通配符
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));//将classpath*:去掉

继续,进入findAllClassPathResources方法中

protected Resource[] findAllClassPathResources(String location) throws IOException { 
   //传入"config/"
	String path = location;
	if (path.startsWith("/")) { 
   
		path = path.substring(1);
	}
	Set<Resource> result = doFindAllClassPathResources(path);//通过jdk的classpathLoader去加载路径
	if (logger.isTraceEnabled()) { 
   
		logger.trace("Resolved classpath location [" + location + "] to resources " + result);
	}
	return result.toArray(new Resource[0]);
}

可以看到,如果这剩下的字符串是以“/”开头的,就将“/”截取掉,然后将路径字符串传给doFindAllClassPathResources,我们再进入doFindAllClassPathResources

protected Set<Resource> doFindAllClassPathResources(String path) throws IOException { 
   //通过jdk的classpathLoader去加载路径,得到个Set<Resource>
	Set<Resource> result = new LinkedHashSet<>(16);
	ClassLoader cl = getClassLoader();
	Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
	while (resourceUrls.hasMoreElements()) { 
   
		URL url = resourceUrls.nextElement();
		result.add(convertClassLoaderURL(url));
	}
	if ("".equals(path)) { 
   
		addAllClassLoaderJarRoots(cl, result);
	}
	return result;
}

可以看到,是通过jdk的classpathLoader去加载路径,得到一个枚举,然后去遍历这个枚举,将其中元素转换为UrlResource。返回Resource的set集合

protected Resource convertClassLoaderURL(URL url) { 
   
	return new UrlResource(url);
}

最后回到findAllClassPathResources,将doFindAllClassPathResources通过jdk的classpathLoader加载路径而得到的结果转换为Resource数组

return result.toArray(new Resource[0]);

以上就是截取掉”classpath*:“之后剩下的字符不含有通配符的处理。
下面我们讲当剩下的字符串中含有通配符,Spring是怎么处理的。
也就是

findPathMatchingResources走向

return findPathMatchingResources(locationPattern);

好,继续,看findPathMatchingResources内部的实现:

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException { 
   
	String rootDirPath = determineRootDir(locationPattern);//获取rootDirPath,其实就是取出前面不含有通配符的字符串,比如"classpath*:config/aa/*.properties"返回classpath*:config/这个文件夹的名称
	String subPattern = locationPattern.substring(rootDirPath.length());//从rootDirPath往后截断,比如"classpath*:config/aa/*.properties",截断后是"*.properties"
	//这个地方再去调用getResources,就用到了递归算法,rootDirPath就是上面的得到的是不含有通配符的,所以会走 findAllClassPathResources,不会走findPathMatchingResources,也就不会死循环
	//最终调用findAllClassPathResources得到路径资源rootDirResources
	Resource[] rootDirResources = getResources(rootDirPath);
	Set<Resource> result = new LinkedHashSet<>(16);
	for (Resource rootDirResource : rootDirResources) { 
   //遍历这个路径资源
		rootDirResource = resolveRootDirResource(rootDirResource);
		URL rootDirUrl = rootDirResource.getURL();//得到url。比如我的是file:/Users/chenzhe/博客写作/攻城掠地/1SpringFamily/Spring/antmatchpath/target/classes/config/
		if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) { 
   //判断rootDirUrl的协议是不是bundle开头的这个对应的协议
			URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
			if (resolvedUrl != null) { 
   
				rootDirUrl = resolvedUrl;
			}
			rootDirResource = new UrlResource(rootDirUrl);
		}
		if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { 
   //判断rootDirUrl的协议是vfs
			result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
		}
		else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) { 
   //判断rootDirUrl的协议是jar
			result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
		}
		else { 
   
			result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
		}
	}
	if (logger.isTraceEnabled()) { 
   
		logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
	}
	return result.toArray(new Resource[0]);
}

首先,第一句

String rootDirPath = determineRootDir(locationPattern);

进去determineRootDir方法

protected String determineRootDir(String location) { 
    //位置字符串location:"classpath*:config/*.properties"
	int prefixEnd = location.indexOf(':') + 1;//prefixEnd:11,也就是:的位置是第11,
	int rootDirEnd = location.length();//rootDirEnd:30,也就是location的长度为30
	//getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd)判断剩下的是不是Pattern模式串,也就是判断是否含有*,**,?等字符
	//其实就是剩下的config/*.properties是否含有通配符,如果含有,就通过"/"截断,就是从后往前一个循环,一直截取到剩下的"/"以前的里面不含有通配符就停止
	//在我们这个例子中就是当截到"config/",这个时候里面没有通配符了,就停止了,然后取(0到rootDirEnd)做为文件夹的名称,
	//说到底就是通过从后截取,每次判断截取的字符串中是否含有通配符,如果没有,就说明是文件夹了,就返回config
	while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) { 
   
			rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;//lastIndexOf截取/前面的字段
	}
	if (rootDirEnd == 0) { 
   
		rootDirEnd = prefixEnd;
	}
	return location.substring(0, rootDirEnd);//返回classpath*:config/这个文件夹的名称
}

这里大家可以看我注释,其实就是先将“:”后面的截取掉,剩下的字符再经过循环截取“/”前面的,直到截取“/”分隔符所得到的字符串里面没有通配符,最终返回的是“:”后面的只是路径的,没有通配符的字符串。
这里举个例子就更清楚了,比如下面这个url串:“classpath*:config/aa/**/cc/dd/.properties”。determineRootDir方法先截取“:”后面的也就剩下字符串”config/aa/**/cc/dd/.properties”。然后后面循环的第一次得到“/”之前的字符串”config/aa/**/cc/dd/”,这时候发现这字符串中含有通配符”*”,就继续从后往前截取,循环,直到找到“config/aa/”。所以determineRootDir其实就是找到”classpath*:“后面不含通配符的路径。

好,再回到findPathMatchingResources继续下一句

String subPattern = locationPattern.substring(rootDirPath.length());

subPattern得到的显而易见,就是上面rootDirPath之后含有通配符的路径字符串,这字符串后面会用到,是做为一个pattren模式串参数传给其他方法,用于匹配算法的,后面我们再说。
继续,下一句:

Resource[] rootDirResources = getResources(rootDirPath);

你会看到,又调用了getResources方法,这里就是一个递归算法了,因为传入的参数是rootDirPath,这个上文中说了,得到的是”classpath*:“后面不含通配符的路径字符串,所以也就不会走findPathMatchingResources,也就不会死循环了,而是走else里面的findAllClassPathResources了:

if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) { 
   
	//带有通配符走这里
	return findPathMatchingResources(locationPattern);
}
else { 
   
	//没有通配符走这里,也就是直接通过路径就能找到,不用去匹配通配符
	return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));//将classpath*:去掉
}

findAllClassPathResources的情况我们上面说过了,所以继续如下:

for (Resource rootDirResource : rootDirResources) { 
   //遍历这个路径资源
	rootDirResource = resolveRootDirResource(rootDirResource);
	URL rootDirUrl = rootDirResource.getURL();//得到url。比如我的是file:/Users/chenzhe/博客写作/攻城掠地/1SpringFamily/Spring/antmatchpath/target/classes/config/
	if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) { 
   //判断rootDirUrl的协议是不是bundle开头的这个对应的协议
		URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
		if (resolvedUrl != null) { 
   
			rootDirUrl = resolvedUrl;
		}
		rootDirResource = new UrlResource(rootDirUrl);
	}
	if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { 
   //判断rootDirUrl的协议是vfs
		result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
	}
	else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) { 
   //判断rootDirUrl的协议是jar
		result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
	}
	else { 
   
		result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
	}
}

可以看到,首先遍历这个rootDirResources,这个rootDirResources通过上面我们可以了解到,其实是符合某一模式串的一组路径资源,所以,比如“classpath*:config/*properties”,得到的rootDirResources就是符合config文件夹下的一组properties的路径资源,
下面:

URL rootDirUrl = rootDirResource.getURL();//得到url。比如我的是file:/Users/chenzhe/博客写作/攻城掠地/1SpringFamily/Spring/antmatchpath/target/classes/config/

得到url比如我的例子就得到的是

file:/Users/chenzhe/博客写作/攻城掠地/1SpringFamily/Spring/antmatchpath/target/classes/config/

然后下面几个if else就是判断rootDirUrl的协议,也就是file还是jar还是vfs等等,
最后,我们的例子走的是

else { 
   
	result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}

继续,进入doFindPathMatchingFileResources,返回的是:

return doFindMatchingFileSystemResources(rootDir, subPattern);//去文件系统里查找资源

继续,进入doFindMatchingFileSystemResources,调用了方法retrieveMatchingFiles:

protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException { 
   //去文件系统里查找资源
	if (logger.isTraceEnabled()) { 
   
		logger.trace("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
	}
	Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);//retrieveMatchingFiles提取匹配的文件,核心方法doRetrieveMatchingFiles
	Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());//拿到
	for (File file : matchingFiles) { 
   //得到匹配的文件
		result.add(new FileSystemResource(file));//用FileSystemResource包装成符合文件系统的Resource,
	}
	return result;//返回结果
}

继续,进入retrieveMatchingFiles方法,其核心方法是doRetrieveMatchingFiles:

doRetrieveMatchingFiles(fullPattern, rootDir, result);//核心方法

继续,进入doRetrieveMatchingFiles,可以看到是一个基于文件系统的遍历做目录或者文件比较,然后getPathMatcher().match(fullPattern, currPath),这个是我们系列文章中讲的match匹配算法:Spring源码之AntPathMatcher(一):doMatch算法。最后返回匹配成功的结果:

	protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException { 
   
		if (logger.isTraceEnabled()) { 
   
			logger.trace("Searching directory [" + dir.getAbsolutePath() +
					"] for files matching pattern [" + fullPattern + "]");
		}
		for (File content : listDirectory(dir)) { 
   //基于文件系统的遍历的一个算法。目录或者文件比较
			String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
			if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) { 
   //matchStart和之前的match区别就是matchStart是一个非完整路径,fullMatch:false
				if (!content.canRead()) { 
   //没有可读权限会日志打一个报错
					if (logger.isDebugEnabled()) { 
   
						logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
								"] because the application is not allowed to read the directory");
					}
				}
				else { 
   
					doRetrieveMatchingFiles(fullPattern, content, result);//递归的一个过程
				}
			}
			if (getPathMatcher().match(fullPattern, currPath)) { 
   //match方法熟悉了吧
				result.add(content);
			}
		}
	}

这里比较深,大家一边对照源码一边看就清楚了。
回过头来findPathMatchingResources,可以看到得到的result最终和findAllClassPathResources一样,转换成了Resource的数组

return result.toArray(new Resource[0]);

好,再回到getResources方法中,继续,如果不是以”classpath*:”开头,就走下面的else:

else { 
   //如果不是以"classpath*:"开头,就会走下面这个
	//可能是打成war包,将这个前缀拿出来。留下":"后面的
	int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
			locationPattern.indexOf(':') + 1);
	//把前缀去掉后再进行一次判断是不是isPattern,含有通配符,然后调用findPathMatchingResources
	if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) { 
   
		return findPathMatchingResources(locationPattern);
	}
	else { 
   //如果不是classpath*:开头,又不是打成war包,就说明是单个文件了,就交给getResourceLoader().getResource,其实也就是交给DefaultResourceLoader
		return new Resource[] { 
   getResourceLoader().getResource(locationPattern)};
	}
}

继续,如果不是以”classpath*:”开头,可能打成war包,那就将war:这个前缀截取掉,然后判断剩下的是否含有通配符,如果是,那么就走findPathMatchingResources()。
如果既不是以”classpath*:”开头,有不是打成了war包,就说明是个单文件了,就如下,用到上一篇说的单文件加载的getResource:

单文件加载的getResource走向

else {//如果不是classpath*:开头,又不是打成war包,就说明是单个文件了,就交给getResourceLoader().getResource,其实也就是交给DefaultResourceLoader
	return new Resource[] {getResourceLoader().getResource(locationPattern)};
}

至此,就讲完了多文件加载资源的处理。总结一下,其实就是三个走向
findPathMatchingResources()
findAllClassPathResources()
getResourceLoader().getResource。
其中findPathMatchingResources比较深些,大家对照源码多看几遍就搞懂了。

今天的文章Spring源码之ResourceLoader(二):PathMatchingResourcePatternResolver实现getResources加载多文件分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/26307.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注