在学习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,主要分两步:

  1. 确定目录并获取目录下所有资源
  2. 在所有获取的资源中进行迭代匹配获取所需资源

代码有点长,就不贴了,有兴趣的小伙伴可以自行查看,位置是:

org.springframework.core.io.support.PathMatchingResourcePatternResolver#findPathMatchingResources

最后放一张 UML 图:

[Ngbatis源码学习][Spring] Spring 的资源管理 ResourceLoader的更多相关文章

  1. 框架源码系列六:Spring源码学习之Spring IOC源码学习

    Spring 源码学习过程: 一.搞明白IOC能做什么,是怎么做的  1. 搞明白IOC能做什么? IOC是用为用户创建.管理实例对象的.用户需要实例对象时只需要向IOC容器获取就行了,不用自己去创建 ...

  2. spring源码学习(三)--spring循环引用源码学习

    在spring中,是支持单实例bean的循环引用(循环依赖)的,循环依赖,简单而言,就是A类中注入了B类,B类中注入了A类,首先贴出我的代码示例 @Component public class Add ...

  3. 【spring源码学习】spring的远程调用实现源码分析

    [一]spring的远程调用提供的基础类 (1)org.springframework.remoting.support.RemotingSupport ===>spring提供实现的远程调用客 ...

  4. 框架源码系列八:Spring源码学习之Spring核心工作原理(很重要)

    目录:一.搞清楚ApplicationContext实例化Bean的过程二.搞清楚这个过程中涉及的核心类三.搞清楚IOC容器提供的扩展点有哪些,学会扩展四.学会IOC容器这里使用的设计模式五.搞清楚不 ...

  5. 【spring源码学习】Spring @PostConstruct和@PreDestroy实例

    在Spring中,既可以实现InitializingBean和DisposableBean接口或在bean配置文件中指定 init-method 和 destroy-method 在初始化和销毁回调函 ...

  6. 【spring源码学习】spring的task配置

    =================spring线程池的配置策略含义========================== id:当配置多个executor时,被@Async("id" ...

  7. 【spring源码学习】spring的aop目标对象中进行自我调用,且需要实施相应的事务定义的解决方案

    转载:http://www.iteye.com/topic/1122740 1.预备知识 aop概念请参考[http://www.iteye.com/topic/1122401]和[http://ji ...

  8. 【spring源码学习】spring配置的事务方式是REQUIRED,但业务层抛出TransactionRequiredException异常问题

    (1)spring抛出异常的点:org.springframework.orm.jpa.EntityManagerFactoryUtils public static DataAccessExcept ...

  9. 【spring源码学习】spring集成orm数据框架

    [一]简易的数据源配置 (1)配置文件 <!--springJdbcTemplemate数据操作配置信息 --> <bean id="driver" class= ...

  10. 【spring源码学习】spring的事务管理的源码解析

    [一]spring事务管理(1)spring的事务管理,是基于aop动态代理实现的.对目标对象生成代理对象,加入事务管理的核心拦截器==>org.springframework.transact ...

随机推荐

  1. vue.draggable中文文档

    http://www.itxst.com/vue-draggable/fiamvqam.html https://blog.csdn.net/a772116804/article/details/10 ...

  2. C#通过泛型实现对子窗体的不同操作

    private void button1_Click(object sender, EventArgs e) { FormOperate<object>();//调用FormOperate ...

  3. 每天学五分钟 Liunx 0101 | 服务篇:创建进程

    创建子进程 上一节说过创建子进程的三种方式: 1. fork 复制进程:fork 会复制当前进程的副本,产生一个新的子进程,父子进程是完全独立的两个进程,他们掌握的资源(环境变量和普通变量)是一样的. ...

  4. zookeeper 集群环境搭建及集群选举及数据同步机制

    本文为博主原创,未经允许不得转载: 目录: 1. 分别创建3个data目录用于存储各节点数据 2. 编写myid文件 3. 编写配置文件     4.分别启动 5.分别查看状态 6. 检查集群复制情况 ...

  5. 2023强网拟态crypto-一眼看出

    1.题目信息 一眼看穿 查看代码  from Crypto.Util.number import * from secret import flag import gmpy2 flag=b'' r = ...

  6. [转帖]TiDB 整体架构

    https://docs.pingcap.com/zh/tidb/stable/tidb-architecture 与传统的单机数据库相比,TiDB 具有以下优势: 纯分布式架构,拥有良好的扩展性,支 ...

  7. [转帖]PostgreSQL中的schema和user

    https://www.cnblogs.com/abclife/p/13905336.html postgresql中,用户创建的所有对象都被创建在指定的schema(或namespace)中.其他用 ...

  8. [转帖]Linux Page cache和Buffer cache

    https://www.cnblogs.com/hongdada/p/16926655.html free 命令常用参数 free 命令用来查看内存使用状况,常用参数如下: -h human-read ...

  9. 【转帖】使用 LuaRocks 安装 Apache APISIX 依赖项时,为什么会导致超时、安装缓慢或安装失败?

    使用 LuaRocks 安装 Apache APISIX 依赖项时,为什么会导致超时.安装缓慢或安装失败?# http://apisix.incubator.apache.org/zh/docs/ap ...

  10. [转帖]Cat导致内存不足原因分析

    背景 线上几亿的数据在回刷的时候容器服务会出现OOM而重启,导致任务中断 内存泄露分析 jmap -histo pid 找出了有几十亿的java.lang.StackTraceElement对象,找不 ...