[Ngbatis源码学习][Spring] Spring 的资源管理 ResourceLoader
在学习Ngbatis的源码时,看到了有关xml文件的加载,涉及到了资源的加载,对相关知识进行总结与整理。
1. 相关类
Resource
AbstractResource
ResourceLoader
DefaultResourceLoader
ResourcePatternResolver
PathMatchingResourcePatternResolver
以下逐一说明。
2. Resource
Resource 是 Spring 框架资源的抽象接口。用于表示应用程序中的各种资源,比如文件、类路径资源、URL等。
Resource 提供了同意的方式来访问这些资源,无论资源处于何处都可以通过 Resource 接口进行操作。
定义的接口如下:
public interface Resource extends InputStreamSource {
// 某个资源是否以物理形式存在
boolean exists();
// 资源的目录读取是否可通过getInputStream()进行读取
default boolean isReadable() {
return this.exists();
}
// 判断资源是否具有开放流的句柄
default boolean isOpen() {
return false;
}
// 是否是一个文件
default boolean isFile() {
return false;
}
// 返回一个URL句柄,如果资源不能够被解析为URL,将抛出IOException
URL getURL() throws IOException;
// 返回一个URI句柄,如果资源不能够被解析为URL,将抛出IOException
URI getURI() throws IOException;
// 返回文件
File getFile() throws IOException;
// 获取可读取的字节通道。在任何时间可读通道上只能有一个读操作正在进行
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(this.getInputStream());
}
// 获取content长度
long contentLength() throws IOException;
// 资源最后一次修改的时间戳
long lastModified() throws IOException;
// 创建此资源的相关资源
Resource createRelative(String relativePath) throws IOException;
// 获取资源的文件名
@Nullable
String getFilename();
// 获取资源描述
String getDescription();
}
Resource 接口的实现类有很多,比如 UrlResource、ClassPathResource、InputStreamResource、FileSystemResource等等,看名称可知道作用,不多赘述。
3. AbstractResource
AbstractResource 是 Resource 的默认实现类,实现了 Resource 大部分方法。
如果需要实现自定义的 Resource 接口,直接去继承 AbstractResource 抽象类,并根据需求重写相关方法就好。
4. ResourceLoader
ResourceLoader 接口提供了一个资源加载策略,是 Spring 资源加载的同一抽象,具体的资源加载由相应的实现类完成。默认实现类是 DafultResourceLoader.。
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = "classpath:";
// 根据所提供的路径 location 获取 Resource 实例,但是不确保 Resource 一定存在。
// 支持 URL、ClassPath、相对路径资源
Resource getResource(String location);
// 返回 ClassLoader 实例
@Nullable
ClassLoader getClassLoader();
}
可以看到 ResourceLoader 提供了两个接口:getResource、getClassLoader。
5. DefaultResourceLoader
DefaultResourceLoader 是 ResourceLoader 的默认实现。代码如下:
public class DefaultResourceLoader implements ResourceLoader {
@Nullable
private ClassLoader classLoader;
private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet(4);
private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap(4);
public DefaultResourceLoader() {
}
public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
public void setClassLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Nullable
public ClassLoader getClassLoader() {
return this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader();
}
// 添加 ProtocolResolver
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.add(resolver);
}
// 获取 ProtocolResolver 集合
public Collection<ProtocolResolver> getProtocolResolvers() {
return this.protocolResolvers;
}
public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {
return (Map)this.resourceCaches.computeIfAbsent(valueType, (key) -> {
return new ConcurrentHashMap();
});
}
public void clearResourceCaches() {
this.resourceCaches.clear();
}
// 根据 location 获取 Resource
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
Iterator var2 = this.getProtocolResolvers().iterator();
Resource resource;
do {
if (!var2.hasNext()) {
if (location.startsWith("/")) {
return this.getResourceByPath(location);
}
if (location.startsWith("classpath:")) {
return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
}
try {
URL url = new URL(location);
return (Resource)(ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
} catch (MalformedURLException var5) {
return this.getResourceByPath(location);
}
}
ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
resource = protocolResolver.resolve(location, this);
} while(resource == null);
return resource;
}
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, this.getClassLoader());
}
protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {
public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
super(path, classLoader);
}
public String getPathWithinContext() {
return this.getPath();
}
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.getPath(), relativePath);
return new ClassPathContextResource(pathToUse, this.getClassLoader());
}
}
}
可以看到代码中涉及到了 ProtocolResolver。重点关注一下 ProtocolResolver 这个类。ProtocolResolver 与 DefaultResourceLoader 密不可分。
ProtocolResolver 翻译一下叫协议解析器,它允许用户自定义协议资源解析策略,作为 DefaultResourceLoader 的SPI,而不需要继承 ResourceLoader 的子类。
浅看一下 ProtocolResolver 的源码:
@FunctionalInterface
public interface ProtocolResolver {
@Nullable
Resource resolve(String location, ResourceLoader resourceLoader);
}
可以看到是个函数式接口,根据传入的 location 和自定义的 ResourceLoader 加载器解析出对应的 Resource 资源。
一般来说如果要实现自定义的 Resource,只需要继承 AbstractResource 就好。但有了 ProtocolResolver 就不需要直接继承 DefaultResourceLoader,实现了 ProtocolResolver 接口也可以实现自定义的 ResourceLoader。
回头看 DefaultResourceLoader 里的 getResource 方法,我们单独拿出来:
// 根据 location 获取 Resource
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
// 获取 ProtocolResolver 集合迭代器
Iterator var2 = this.getProtocolResolvers().iterator();
Resource resource;
do {
// 如果获取不到 ProtocolResolver 元素
if (!var2.hasNext()) {
// 如果是以 / 开头
if (location.startsWith("/")) {
return this.getResourceByPath(location);
}
// 如果是以 classpath: 开头
if (location.startsWith("classpath:")) {
return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
}
try {
// 构造 URL 进行资源定位
URL url = new URL(location);
return (Resource)(ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
} catch (MalformedURLException var5) {
return this.getResourceByPath(location);
}
}
// 获取 ProtocolResolver 并调用 resolve 接口获取 Resource
ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
resource = protocolResolver.resolve(location, this);
} while(resource == null);
return resource;
}
6. ResourcePatternResolver
ResourcePatternResolver 是 ResourceLoader 的扩展。
代码如下:
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
对比一下 ResourceLoader 的 getResource 方法就会发现,ResourceLoader 的 getResource 通过传入 location 来返回一个 Resource,当需要加载多个资源的时候就必须多次调用 getResourece。
而 ResourcePatternResolver 作为 ResourceLoader 的扩展定义了可以根据指定的资源路径匹配模式每次返回多个 Resource 实例。
可以看到新增了新的协议前缀 classpath*: 。
7. PathMatchingResourcePatternResolver
PathMatchingResourcePatternResolver 是 ResourcePatternResolver 很常用的子类,它新增了 Ant 风格的路径匹配模式。
扩展:Ant 风格匹配模式
Ant 风格的路径匹配规则是一种常用的路径模式匹配规则
包括以下几种模式:
?
: 匹配任意单个字符*
: 匹配任意数量,包括零个的字符**
: 匹配任意数量,包括零个的目录路径
举例说明:
**/*.xml
/user/**
/user/*/profile
PathMatchingResourcePatternResolver 有三种构造方法:
public PathMatchingResourcePatternResolver() {
this.resourceLoader = new DefaultResourceLoader();
}
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
public PathMatchingResourcePatternResolver(@Nullable ClassLoader classLoader) {
this.resourceLoader = new DefaultResourceLoader(classLoader);
}
可以看到,如果不指定 ResourceLoader 资源加载器的话,默认都是使用 DefaultResourceLoader。
关于获取 Resource 提供了两种方法:
// 委托给相应的 ResourceLoader 获取
public Resource getResource(String location) {
return this.getResourceLoader().getResource(location);
}
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
// 是否以 classpath*: 开头
if (locationPattern.startsWith("classpath*:")) {
return this.getPathMatcher().isPattern(locationPattern.substring("classpath*:".length())) ? this.findPathMatchingResources(locationPattern) : this.findAllClassPathResources(locationPattern.substring("classpath*:".length()));
} else {
int prefixEnd = locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 : locationPattern.indexOf(58) + 1;
return this.getPathMatcher().isPattern(locationPattern.substring(prefixEnd)) ? this.findPathMatchingResources(locationPattern) : new Resource[]{this.getResourceLoader().getResource(locationPattern)};
}
}
getResource 方法很好理解,不多做赘述。
getResources 方法,逻辑的流程图如下:
getResources 中调用了 findAllClassPathResources 方法,该方法返回 classes 路径下和所有 jar 包中相匹配的资源。
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (location.startsWith("/")) {
path = location.substring(1);
}
Set<Resource> result = this.doFindAllClassPathResources(path);
if (logger.isTraceEnabled()) {
logger.trace("Resolved classpath location [" + location + "] to resources " + result);
}
return (Resource[])result.toArray(new Resource[0]);
}
// 获取当前路径下的所有资源
protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
Set<Resource> result = new LinkedHashSet(16);
ClassLoader cl = this.getClassLoader();
Enumeration<URL> resourceUrls = cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path);
while(resourceUrls.hasMoreElements()) {
URL url = (URL)resourceUrls.nextElement();
result.add(this.convertClassLoaderURL(url));
}
if (!StringUtils.hasLength(path)) {
this.addAllClassLoaderJarRoots(cl, result);
}
return result;
}
getResources 中还调用了另一个方法 findPathMatchingResources,主要分两步:
- 确定目录并获取目录下所有资源
- 在所有获取的资源中进行迭代匹配获取所需资源
代码有点长,就不贴了,有兴趣的小伙伴可以自行查看,位置是:
org.springframework.core.io.support.PathMatchingResourcePatternResolver#findPathMatchingResources
最后放一张 UML 图:
本文来自博客园,作者:knqiufan,转载请注明原文链接:https://www.cnblogs.com/knqiufan/p/17908673.html