每次看到spring能自动扫描jar和classpath下面的资源就感到非常好奇,所以趁这2天没有事情就阅读一下spring的PathMatchingResourcePatternResolver类,主要查看的解析包然后获取所有资源的流程
1.PathMatchingResourcePatternResolver的结构
2.实例化一个PathMatchingResourcePatternResolver类
//初始化一个PathMatchingResourcePatternResolver
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
//根据路径匹配,获取classpath下面(包含jar里面)的资源文件
Resource[] resources = resourceResolver.getResources("classpath*:*/version.properties");
3.getResources方法
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
//根据classpath*:前缀判断走那条路
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
//getPathMatcher()获取的实例类为AntPathMatcher,AntPathMatcher用来配置路径上的通配符,这个以后在讲
//然后调用isPattern方法判断前缀中是否有通配符号*和?
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
//查出通配符符合的所有资源
return findPathMatchingResources(locationPattern);
}
else {
//获取路径下面的所有资源
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
//下面是从war包和直接获取,就不讲了
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
return findPathMatchingResources(locationPattern);
}
else {
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
4.findPathMatchingResources方法
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
//获取根路径,也就是classpath*:
String rootDirPath = determineRootDir(locationPattern);
//获取子路径
String subPattern = locationPattern.substring(rootDirPath.length());
//获取根路径的觉得路径,包含jar包里面的路径
Resource[] rootDirResources = getResources(rootDirPath);
Set<Resource> result = new LinkedHashSet<>(16);
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirUrl = rootDirResource.getURL();
if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
if (resolvedUrl != null) {
rootDirUrl = resolvedUrl;
}
rootDirResource = new UrlResource(rootDirUrl);
}
//判断路径前缀是否为vfs,
if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
}
//判断路径前缀是否为jar,
else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
}
else {
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[0]);
}
5.doFindPathMatchingJarResources从jar文件读取配置文件
protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern)
throws IOException {
URLConnection con = rootDirURL.openConnection();
JarFile jarFile;
String jarFileUrl;
String rootEntryPath;
boolean closeJarFile;
if (con instanceof JarURLConnection) {
//打开jar的URL链接,获取jar包信息
JarURLConnection jarCon = (JarURLConnection) con;
ResourceUtils.useCachesIfNecessary(jarCon);
jarFile = jarCon.getJarFile();
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
closeJarFile = !jarCon.getUseCaches();
}
else {
// 如果不是jarURL链接,就解析路径,构建jarfile对象
String urlFile = rootDirURL.getFile();
try {
int separatorIndex = urlFile.indexOf(ResourceUtils.WAR_URL_SEPARATOR);
if (separatorIndex == -1) {
separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
}
if (separatorIndex != -1) {
jarFileUrl = urlFile.substring(0, separatorIndex);
rootEntryPath = urlFile.substring(separatorIndex + 2);
jarFile = getJarFile(jarFileUrl);
}
else {
jarFile = new JarFile(urlFile);
jarFileUrl = urlFile;
rootEntryPath = "";
}
closeJarFile = true;
}
catch (ZipException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping invalid jar classpath entry [" + urlFile + "]");
}
return Collections.emptySet();
}
}
try {
if (logger.isDebugEnabled()) {
logger.debug("Looking for matching resources in jar file [" + jarFileUrl + "]");
}
if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
// Root entry path must end with slash to allow for proper matching.
// The Sun JRE does not return a slash here, but BEA JRockit does.
rootEntryPath = rootEntryPath + "/";
}
//读取所有jar里面的文件,然后与路径进行配置,如果配置成功就添加到结果中进行返回
Set<Resource> result = new LinkedHashSet<>(8);
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
String entryPath = entry.getName();
if (entryPath.startsWith(rootEntryPath)) {
String relativePath = entryPath.substring(rootEntryPath.length());
if (getPathMatcher().match(subPattern, relativePath)) {
result.add(rootDirResource.createRelative(relativePath));
}
}
}
return result;
}
finally {
if (closeJarFile) {
jarFile.close();
}
}
}
6.findPathMatchingResources方法
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (path.startsWith("/")) {
path = path.substring(1);
}
//获取路径的地方
Set<Resource> result = doFindAllClassPathResources(path);
if (logger.isDebugEnabled()) {
logger.debug("Resolved classpath location [" + location + "] to resources " + result);
}
return result.toArray(new Resource[0]);
}
protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
Set<Resource> result = new LinkedHashSet<>(16);
ClassLoader cl = getClassLoader();
//获取当前classpath的路径
Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
while (resourceUrls.hasMoreElements()) {
URL url = resourceUrls.nextElement();
result.add(convertClassLoaderURL(url));
}
//获取当前classpath下的jar根路径
if ("".equals(path)) {
// The above result is likely to be incomplete, i.e. only containing file system references.
// We need to have pointers to each of the jar files on the classpath as well...
addAllClassLoaderJarRoots(cl, result);
}
return result;
}
protected void addAllClassLoaderJarRoots(@Nullable ClassLoader classLoader, Set<Resource> result) {
if (classLoader instanceof URLClassLoader) {
try {
//从classloader中获取所有加入文件
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
try {
UrlResource jarResource = new UrlResource(
ResourceUtils.JAR_URL_PREFIX + url + ResourceUtils.JAR_URL_SEPARATOR);
if (jarResource.exists()) {
result.add(jarResource);
}
}
catch (MalformedURLException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot search for matching files underneath [" + url +
"] because it cannot be converted to a valid 'jar:' URL: " + ex.getMessage());
}
}
}
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot introspect jar files since ClassLoader [" + classLoader +
"] does not support 'getURLs()': " + ex);
}
}
}
if (classLoader == ClassLoader.getSystemClassLoader()) {
// //从java.class.path路径中获取所有加入文件
addClassPathManifestEntries(result);
}
if (classLoader != null) {
try {
//递归从父级classLoader中加载jar文件
addAllClassLoaderJarRoots(classLoader.getParent(), result);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot introspect jar files in parent ClassLoader since [" + classLoader +
"] does not support 'getParent()': " + ex);
}
}
}
}