Spring的统一资源加载策略

写在前面

什么是资源呢?最基本的本地磁盘上的一个文件,远端某服务器的一个图片,本地的一个jar包,本地的一个jar包内的内嵌jar包/内嵌class文件/内嵌properties文件,这些都是资源,为了能够满足各种资源的读取,spring定义了统一的资源加载策略。

1:Resource接口

该接口在spring的spring-core模块中,完整类名org.springframework.core.io.Resource,该接口要用来抽象资源的定义的,最终资源会封装为该接口类型的一个对象,然后使用该对象来操作资源,源码如下:

public interface Resource extends InputStreamSource {
    
    

	// 判断文件是否存在
	boolean exists();

	// 判断文件是否可读,该方法是一个default方法,提供默认实现
	default boolean isReadable() {
    
    
		return exists();
	}

	// 判断文件句柄是否被打开,该方法是以default方法,提供默认实现
	default boolean isOpen() {
    
    
		return false;
	}

	// 判断是否为一个文件,该方法是一个default方法,提供默认实现
	default boolean isFile() {
    
    
		return false;
	}

	// 获取文件对应URL(如http://,jar://,ftp://,file://等)
	URL getURL() throws IOException;
	// 返回资源的URI
	URI getURI() throws IOException;
	// 获取文件对象
	File getFile() throws IOException;

	// 返回nio的ReadableByteChannel对象,该方法为default方法,直接使用
	// Channels的静态方法newChannel创建
	default ReadableByteChannel readableChannel() throws IOException {
    
    
		return Channels.newChannel(getInputStream());
	}

	// 返回文件的长度
	long contentLength() throws IOException;

	// 返回文件的上次修改时间
	long lastModified() throws IOException;
	
	// 根据当前资源的相对路径,创建其它资源的Resource
	Resource createRelative(String relativePath) throws IOException;

	// 获取文件的名称
	@Nullable
	String getFilename();

	// 获取资源的描述
	String getDescription();

}

其继承了接口org.springframework.core.io.InputStreamSource,该接口只定义了一个获取输入流的方法,源码如下:

public interface InputStreamSource {
    
    
	// 获取资源对应的输入流对象
	InputStream getInputStream() throws IOException;

}

1.1:AbstractResource

该类是Resource接口的抽象实现类,全限定名是org.springframework.core.io.AbstractResource,该类定义如下:

public abstract class AbstractResource implements Resource {
    
    
	@Override
	public boolean exists() {
    
    
		// 直接根据File获取是否存在,如果无法正常获取则进入异常
		// 尝试其它方式获取
		try {
    
    
			return getFile().exists();
		}
		catch (IOException ex) {
    
    
			// 尝试通过InputStream获取,如果是能够close则返回true
			// 否则进入异常返回false代表资源不存在
			try {
    
    
				getInputStream().close();
				return true;
			}
			catch (Throwable isEx) {
    
    
				return false;
			}
		}
	}

	@Override
	public boolean isReadable() {
    
    
		// 存在即可读
		return exists();
	}

	@Override
	public boolean isOpen() {
    
    
		return false;
	}

	@Override
	public boolean isFile() {
    
    
		return false;
	}

	@Override
	public URL getURL() throws IOException {
    
    
		// 抛出异常,强制交给子类实现
		throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
	}

	// URL转URI
	@Override
	public URI getURI() throws IOException {
    
    
		URL url = getURL();
		try {
    
    
			return ResourceUtils.toURI(url);
		}
		catch (URISyntaxException ex) {
    
    
			throw new NestedIOException("Invalid URI [" + url + "]", ex);
		}
	}

	// 直接抛出异常强制子类实现
	@Override
	public File getFile() throws IOException {
    
    
		throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
	}

	@Override
	public ReadableByteChannel readableChannel() throws IOException {
    
    
		return Channels.newChannel(getInputStream());
	}

	// 获取文件长度,其实就是文件内容的byte数
	@Override
	public long contentLength() throws IOException {
    
    
		// 获取输入流
		InputStream is = getInputStream();
		try {
    
    
			long size = 0;
			byte[] buf = new byte[256];
			int read;
			// 通过while循环读取到byte数组中累加获取最终长度
			while ((read = is.read(buf)) != -1) {
    
    
				size += read;
			}
			return size;
		}
		finally {
    
    
			try {
    
    
				is.close();
			}
			catch (IOException ex) {
    
    
			}
		}
	}

	// 获取资源的最后修改时间
	@Override
	public long lastModified() throws IOException {
    
    
		File fileToCheck = getFileForLastModifiedCheck();
		long lastModified = fileToCheck.lastModified();
		if (lastModified == 0L && !fileToCheck.exists()) {
    
    
			throw new FileNotFoundException(getDescription() +
					" cannot be resolved in the file system for checking its last-modified timestamp");
		}
		return lastModified;
	}

	protected File getFileForLastModifiedCheck() throws IOException {
    
    
		return getFile();
	}

	// 根据当前资源的相对路径创建其它新的资源对象,直接抛出
	// FileNotFoundException强制子类实现
	@Override
	public Resource createRelative(String relativePath) throws IOException {
    
    
		throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
	}

	// 获取文件名称,直接返回null,交给但不强制子类实现
	@Override
	@Nullable
	public String getFilename() {
    
    
		return null;
	}

	@Override
	public boolean equals(Object other) {
    
    
		return (this == other || (other instanceof Resource &&
				((Resource) other).getDescription().equals(getDescription())));
	}

	@Override
	public int hashCode() {
    
    
		return getDescription().hashCode();
	}

	@Override
	public String toString() {
    
    
		return getDescription();
	}

}

如果是我们想要自定义Resource直接继承该抽象类就可以了。

2:ResourceLoader接口

该接口在spring的spring-core模块中,限定名是org.springframework.core.io.ResourceLoader。该接口用于定义资源的加载,其实内部是使用的Resource接口,源码不是很复杂,如下:

public interface ResourceLoader {
    
    
	
	// public static final String CLASSPATH_URL_PREFIX = "classpath:";
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

	// 根据位置获取资源对象
	// 值可以是URL如file:c:/test.txt,classpath如classpath:resources.xml
	// 也可以是相对路径,如WEB-INF/web.xml
	Resource getResource(String location);

	// 获取类加载器
	@Nullable
	ClassLoader getClassLoader();

}

2.1:DefaultResourceLoader

该类是ResourceLoader的默认实现类,注意是一个类,而不是抽象类,先来看一个使用的例子,代码:

@Test
public void testResourceLoader() throws Exception {
    
    
    // 定义resourceloader
    ResourceLoader resourceLoader = new DefaultResourceLoader();
    // http://img.ewebweb.com/uploads/20191006/20/1570365161-shmEFlWfHU.jpg
    String resourceLocation = "https://www.baidu.com/";
    Resource resource = resourceLoader.getResource(resourceLocation);
    System.out.println(resource.getFilename());
    System.out.println(resource.contentLength());
    System.out.println(resource.getURL());
    System.out.println(resource.getURI());

    InputStream inputStream = resource.getInputStream();

    StringBuffer out = new StringBuffer();
    byte[] b = new byte[4096];
    for (int n; (n = inputStream.read(b)) != -1; ) {
    
    
        out.append(new String(b, 0, n));
    }
    System.out.println(out);
}

测试:

2443
https://www.baidu.com/
https://www.baidu.com/
<!DOCTYPE html>
<!--STATUS OK--><html> ...snip...
title>百度一下,你就知道</title>

我们来就着例子分析下org.springframework.core.io.ResourceLoader#getResource部分源码:

org.springframework.core.io.DefaultResourceLoader#getResource
@Override
publicResource getResource(String location) {
    
    
	// 断言判断
	Assert.notNull(location, "Location must not be null");
	// <DefaultResourceLoader#getResource_1>
	for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
    
    
		Resource resource = protocolResolver.resolve(location, this);
		if (resource != null) {
    
    
			return resource;
		}
	}
	// 如果是以/开头则调用getResourceByPath方法,一般子类重载该方法就可以了
	if (location.startsWith("/")) {
    
    
		// <DefaultResourceLoader#getResource_2>
		return getResourceByPath(location);
	}
	// 如果是以classpath开头,则返回ClassPathResouce对象
	else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
    
    
		return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
	}
	else {
    
    
		try {
    
    
			// 根据位置创建url对象
			URL url = new URL(location);
			// <DefaultResourceLoader#getResource_3>
			return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
		}
		catch (MalformedURLException ex) {
    
    
			// No URL -> resolve as resource path.
			return getResourceByPath(location);
		}
	}
}

<DefaultResourceLoader#getResource_1>处本例中没有元素,后续会分析,这里暂时忽略,如下图:
在这里插入图片描述
这是协议扩展使用的。<DefaultResourceLoader#getResource_2>处源码:

protected Resource getResourceByPath(String path) {
    
    
	return new ClassPathContextResource(path, getClassLoader());
}

直接使用ClassPathContextResource类创建对象作为Resource使用。<DefaultResourceLoader#getResource_3>是我们会真正执行到的代码位置,会首先通过ResourceUtils.isFileURL(url)判断是否是文件地址,比如file://就是文件地址,运行效果如下:
在这里插入图片描述
我们这里是https协议,所以结果为false,那么<DefaultResourceLoader#getResource_3>处的最终执行的代码就是new UrlResource(url)),即最终返回UrlResouce作为Resource。
<DefaultResourceLoader#getResource_1>并没有ResourceResolver,这是spring留给我们的扩展口,可以通过实现org.springframework.core.io.ProtocolResolver接口,然后通过方法org.springframework.core.io.DefaultResourceLoader#addProtocolResolver添加,该方法源码如下:

org.springframework.core.io.DefaultResourceLoader#addProtocolResolver
public void addProtocolResolver(ProtocolResolver resolver) {
    
    
	Assert.notNull(resolver, "ProtocolResolver must not be null");
	// private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
	this.protocolResolvers.add(resolver);
}

下面我们来自定义一个schema为dongshidaddy:的自定义协议解析器,在这之前我们先来看下ProtocolResolver的源码:

@FunctionalInterface
public interface ProtocolResolver {
    
    

	@Nullable
	Resource resolve(String location, ResourceLoader resourceLoader);

}

注意到注解@FunctionalInterface,该注解用于编译阶段,限制接口中只能有一个抽象方法,如下就是不可以的:
在这里插入图片描述
如下一个抽象,一个default是可以的:
在这里插入图片描述
只有一个抽象方法自然也是可以的:
在这里插入图片描述
接口中的方法Resource resolve(String location, ResourceLoader resourceLoader)就是我们需要实现的唯一的方法,最终解析我们自定义的协议,返回Resource就可以了,如下自定义协议解析器:

public class DongshiDaddyProtocolResolver implements ProtocolResolver {
    
    
    private static final String DONGSHIDADDY_SCHEMA="dongshidaddy:";

    @Override
    public Resource resolve(String location, ResourceLoader resourceLoader) {
    
    
        if (!location.startsWith(DONGSHIDADDY_SCHEMA))
            return null;
        String realPath = location.substring(13);
        String classPath = "classpath:" + realPath;

        return resourceLoader.getResource(classPath);
    }
}

测试代码:

@Test
public void testSeftDefineProtocolResolver() throws Exception {
    
    
    DefaultResourceLoader resourceLoader=new DefaultResourceLoader();
    resourceLoader.addProtocolResolver(new DongshiDaddyProtocolResolver());
    Resource resource = resourceLoader.getResource("dongshidaddy:test.txt");
    InputStream inputStream = resource.getInputStream();
    StringBuffer out = new StringBuffer();
    byte[] b = new byte[4096];
    for (int n; (n = inputStream.read(b)) != -1; ) {
    
    
        out.append(new String(b, 0, n));
    }
    System.out.println(out);
}

测试的文件如下:
在这里插入图片描述
运行:

testing!!!

Process finished with exit code 0

如下debugorg.springframework.core.io.DefaultResourceLoader#getResource
在这里插入图片描述
因此可以说ProtocolResolver是spring留给我们的扩展点

接下来我们单起篇幅来看下主要的ResouceLoader都有哪些。

3:FileSystemResourceLoader

通过该资源加载器返回的资源类是FileSystemContextResource

3.1:测试代码

@Test
public void testFileSystemResourceLoader() {
    
    
    String path = "d:\\test\\mystarter-1.0-SNAPSHOT.jar";
    FileSystemResourceLoader fileSystemResourceLoader = new FileSystemResourceLoader();
    Resource resource = fileSystemResourceLoader.getResource(path);
    System.out.println("resource instanceof FileSystemResource: " + (resource instanceof FileSystemResource));
    System.out.println("resource.getFilename() is: " + resource.getFilename());
}

首先执行如下代码:

org.springframework.core.io.DefaultResourceLoader#getResource
@Override
public Resource getResource(String location) {
    
    
	Assert.notNull(location, "Location must not be null");

	...snip...
	else {
    
    
		try {
    
    
			...snip...
		}
		catch (MalformedURLException ex) {
    
    
			// <2021-02-22 13:38>
			return getResourceByPath(location);
		}
	}
}

会执行到代码<2021-02-22 13:38>处,最终调用到方法org.springframework.core.io.FileSystemResourceLoader#getResourceByPath,源码如下:

org.springframework.core.io.FileSystemResourceLoader#getResourceByPath
@Override
protected Resource getResourceByPath(String path) {
    
    
	// 处理斜杠开头的情况,一般在linux环境,本例测试是windows环境
	// 值是d:\\test\\mystarter-1.0-SNAPSHOT.jar,因此if为false
	if (path.startsWith("/")) {
    
    
		path = path.substring(1);
	}
	// <2021-02-22 10:41>,直接new一个FileSystemContextResource
	return new FileSystemContextResource(path);
}

<2021-02-22 10:41>处直接创建FileSystemContextResource类,该类是一个私有内部类,源码如下:

org.springframework.core.io.FileSystemResourceLoader.FileSystemContextResource
private static class FileSystemContextResource extends FileSystemResource implements ContextResource {
    
    
	// <2021-02-22 10:41> 构造函数
	public FileSystemContextResource(String path) {
    
    
		super(path);
	}

	@Override
	public String getPathWithinContext() {
    
    
		return getPath();
	}
}

在构造函数中super(path);直接调用如下代码完成创建:

org.springframework.core.io.FileSystemResource#FileSystemResource(java.lang.String)
public FileSystemResource(String path) {
    
    
	Assert.notNull(path, "Path must not be null");
	// <2021-02-22 10:56> 处理路径为标准格式
	this.path = StringUtils.cleanPath(path);
	// 创建文件对象
	this.file = new File(path);
	// 记录文件路径
	this.filePath = this.file.toPath();
}

<2021-02-22 10:56>处可以参考这里

4:ClassRelativeResourceLoader

该类是基于class的相对路径来加载资源,先来看个例子,在resources目录下创建test.xml,然后编写如下测试代码:

@Test
public void testClassRelativeResourceLoader() throws Exception {
    
    
    ResourceLoader resourceLoader = new ClassRelativeResourceLoader(MyFirstSpringBean.class);
    Resource resource = resourceLoader.getResource("../../test.xml");
    File file = resource.getFile();
    System.out.println(file.getPath());
}

编译后MyFirstSpringBean.classtest.xml路径关系如下图:
在这里插入图片描述
执行测试代码:

E:\workspace-idea\java-life\target\classes\test.xml

Process finished with exit code 0

来看下是否为实际的系统路径:
在这里插入图片描述
来看下源码执行过程,首先执行到代码:

@Override
public Resource getResource(String location) {
    
    
	Assert.notNull(location, "Location must not be null");

	...snip...
	else {
    
    
		try {
    
    
			...snip...
		}
		catch (MalformedURLException ex) {
    
    
			// <2021-02-22 11:42>
			return getResourceByPath(location);
		}
	}
}

<2021-02-22 11:42>处执行代码org.springframework.core.io.ClassRelativeResourceLoader#getResourceByPath,源码如下:

org.springframework.core.io.ClassRelativeResourceLoader#getResourceByPath
@Override
protected Resource getResourceByPath(String path) {
    
    
	return new ClassRelativeContextResource(path, this.clazz);
}

继续:

org.springframework.core.io.ClassRelativeResourceLoader.ClassRelativeContextResource#ClassRelativeContextResource
public ClassRelativeContextResource(String path, Class<?> clazz) {
    
    
	// <2021-02-22 11:43>
	super(path, clazz);
	this.clazz = clazz;
}

<2021-02-22 11:43>处执行如下代码:

org.springframework.core.io.ClassPathResource#ClassPathResource(java.lang.String, java.lang.Class<?>)
public ClassPathResource(String path, @Nullable Class<?> clazz) {
    
    
	Assert.notNull(path, "Path must not be null");
	// 该处处理前路径为../../test.xml,处理后路径依然是../../test.xml
	this.path = StringUtils.cleanPath(path);
	// 记录clazz到属性中
	this.clazz = clazz;
}

最后我们来看下测试代码File file = resource.getFile();是如何通过class和相对路径来获取文件对象的,执行到代码:

org.springframework.core.io.AbstractFileResolvingResource#getFile()
@Override
public File getFile() throws IOException {
    
    
	// <2021-02-22 11:59>
	URL url = getURL();
	if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
    
    
		return VfsResourceDelegate.getResource(url).getFile();
	}
	return ResourceUtils.getFile(url, getDescription());
}

直接看<2021-02-22 11:59>处源码:

org.springframework.core.io.ClassPathResource#getURL
@Override
public URL getURL() throws IOException {
    
    
	// <2021-02-22 12:00>
	URL url = resolveURL();
	if (url == null) {
    
    
		throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
	}
	return url;
}

继续看<2021-02-22 12:00>处源码:

@Nullable
protected URL resolveURL() {
    
    
	// 会进入if
	if (this.clazz != null) {
    
    
		// 直接通过java.lang.Class#getResource获取URL
		return this.clazz.getResource(this.path);
	}
	else if (this.classLoader != null) {
    
    
		return this.classLoader.getResource(this.path);
	}
	else {
    
    
		return ClassLoader.getSystemResource(this.path);
	}
}

这里就通过class对象获取了对应文件的URL了,后续也都是基于该URL来操作了。

5:ResourcePatternResolver

ResourceLoader接口定义的API只能通过一个位置获取资源对象,如果是我们有根据多个路径获取多个资源的需求,除了for循环一个一个的获取,别无他法,为了解决这个问题,spring定义了接口org.springframework.core.io.support.ResourcePatternResolver,源码如下:

public interface ResourcePatternResolver extends ResourceLoader {
    
    
	// 预定义的基于classpath的多个路径协议
	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

	// 根据模式路径获取多个资源的方法
	Resource[] getResources(String locationPattern) throws IOException;

}

最常用的一个子类是PathMatchingResourcePatternResolver,我们先来看下该类的主要的用法。

5.1:PathMatchingResourcePatternResolver用法

5.1.1:加载单个文件

  • 代码
@Test
public void testPathMatchingResourcePatternResolverWithClass() throws Exception {
    
    
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    Resource[] resources = resolver.getResources("yudaosourcecode/spring/DiUser.class");
    System.out.println("resources.length is: " + resources.length);
    for (Resource resource : resources) {
    
    
        System.out.println(resource.getClass());
        System.out.println(resource.getFile().getPath());
    }
}
  • 测试
resources.length is: 1
class org.springframework.core.io.DefaultResourceLoader$ClassPathContextResource
E:\workspace-idea\java-life\target\classes\yudaosourcecode\spring\DiUser.class

5.1.2:通过classpath*加载多个文件

  • 代码
@Test
public void testPathMatchingResourcePatternResolverWithClasspathStart() throws IOException {
    
    
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    Resource[] resources = resolver.getResources("classpath*:yudaosourcecode/spring/*.class");
    System.out.println("resources.length is: " + resources.length);
    for (Resource resource : resources) {
    
    
        System.out.println(resource.getClass());
        System.out.println(resource.getFile().getPath());
    }
}
  • 测试
resources.length is: 9
class org.springframework.core.io.FileSystemResource
E:\workspace-idea\java-life\target\classes\yudaosourcecode\spring\ConstructorDi.class
class org.springframework.core.io.FileSystemResource
E:\workspace-idea\java-life\target\classes\yudaosourcecode\spring\DiPerson.class
class org.springframework.core.io.FileSystemResource
E:\workspace-idea\java-life\target\classes\yudaosourcecode\spring\DiUser.class
class org.springframework.core.io.FileSystemResource
E:\workspace-idea\java-life\target\classes\yudaosourcecode\spring\DongshiDaddyProtocolResolver.class
class org.springframework.core.io.FileSystemResource
...snip...

5.2:PathMatchingResourcePatternResolver源码

该类的路径是org.springframework.core.io.support.PathMatchingResourcePatternResolver,该类除了支持ResourcePatternResolver新增的classpath*:外,还支持Ant风格的路径匹配模式,如**/*/*.xml。来看下源码,首先到方法org.springframework.core.io.support.PathMatchingResourcePatternResolver#getResources,源码如下:

org.springframework.core.io.support.PathMatchingResourcePatternResolver#getResources
@Override
public Resource[] getResources(String locationPattern) throws IOException {
    
    
	Assert.notNull(locationPattern, "Location pattern must not be null");
	// 以CLASSPATH_ALL_URL_PREFIX,即org.springframework.core.io.support.ResourcePatternResolver#CLASSPATH_ALL_URL_PREFIX 
	// String CLASSPATH_ALL_URL_PREFIX = "classpath*:";开头
	if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
    
    
		// <2021-02-22 15:50>
		// 通过Ant路径匹配器AntPathMatcher判断路径是否为Ant风格路径
		if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
    
    
			// <2021-02-22 15:53>
			return findPathMatchingResources(locationPattern);
		}
		// 如果是路径不是Ant风格的
		else {
    
    
			// <2021-02-22 15:54>
			return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
		}
	}
	// 如果不是以classpath*开头的
	else {
    
    
		int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
				locationPattern.indexOf(':') + 1);
		if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
    
    
			return findPathMatchingResources(locationPattern);
		}
		else {
    
    
			// a single resource with the given name
			return new Resource[] {
    
    getResourceLoader().getResource(locationPattern)};
		}
	}
}

<2021-02-22 15:50>处代码,方法getPathMatcher()源码如下:

public PathMatcher getPathMatcher() {
    
    
	// private PathMatcher pathMatcher = new AntPathMatcher();
	// 返回接口PathMatcher的实现类AntPathMatcher
	return this.pathMatcher;
}

接着调用方法org.springframework.util.AntPathMatcher#isPattern源码如下:

@Override
public boolean isPattern(String path) {
    
    
	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;
			continue;
		}
		if (c == '}' && uriVar) {
    
    
			return true;
		}
	}
	return false;
}

主要是判断是否包含*,?,{}等通配符,有的话就返回true,否则返回false。<2021-02-22 15:53>处代码源码如下:

org.springframework.core.io.support.PathMatchingResourcePatternResolver#findPathMatchingResources
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
    
    
	// <2021-02-22 15:54> 获取路径去除通配符的部分,作为根路径
	String rootDirPath = determineRootDir(locationPattern);
	// 获取通配符部分路径,如classpath*:yudaosourcecode/spring/*.class
	// subPattern结果就是*.class
	String subPattern = locationPattern.substring(rootDirPath.length());
	// 再重复调用getResources获取所有资源的Resouces数组
	// 这里一般就是一个文件夹对应的资源,所以长度一般为1
	Resource[] rootDirResources = getResources(rootDirPath);
	// 最终我们需要的资源的结果集合
	Set<Resource> result = new LinkedHashSet<>(16);
	// 循环所有的的资源,只留下我们需要的资源
	for (Resource rootDirResource : rootDirResources) {
    
    
		// 该方法直接返回自己,因此该行代码目前没有实际作用
		rootDirResource = resolveRootDirResource(rootDirResource);
		URL rootDirUrl = rootDirResource.getURL();
		// 这里处理bundle,不是和了解,先忽略,有需要再看
		if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
    
    
			URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
			if (resolvedUrl != null) {
    
    
				rootDirUrl = resolvedUrl;
			}
			rootDirResource = new UrlResource(rootDirUrl);
		}
		// 处理vfg,不是很了解,先忽略
		if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
    
    
			result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
		}
		// 处理jar:,war:,等协议路径
		else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
    
    
			result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
		}
		// 处理其他路径协议,这里包括classpath*:
		else {
    
    
		// 该处代码根据资源和需要的文件通配符,从文件夹资源下获取对应的资源,如果路径为classpath*:yudaosourcecode/spring/*.class,则rootDirResource为
		// URL [file:/E:/workspace-idea/java-life/target/classes/yudaosourcecode/spring/]
		// subPattern为*.class
		// 比较复杂,先不深究
		result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
		}
	}
	if (logger.isTraceEnabled()) {
    
    
		logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
	}
	return result.toArray(new Resource[0]);
}

猜你喜欢

转载自blog.csdn.net/wang0907/article/details/113913888