Spring框架源码分析(IoC):Resource、ResourceLoader和容器之间的关系
系列文章主页
Resource和ResourceLoader
- Java中资源可以被抽象成URL,Spring中将对物理资源的访问方式抽象成了Resource,Spring中的Resource访问策略有多种实现。
- Spring可以利用一种利器来自动选择资源加载方式,这就是——ResourceLoader。
Resource接口体系
- Resource接口是Spring中访问资源的抽象,Resource接口本身只提供了规定,下面有很多的实现类。都是从实际的系统底层资源进行抽象的资源描述符。一般来说在Spring中是将资源描述为URL格式和Ant风格带通配符的资源地址。
- Resource接口的家族体系类图如下图所示:
- 资源描述的顶级接口——
Resource
接口
Resource
接口定义了资源常见的操作,抽象出了一些通用方法,再由不同的实现类去自定义。直接上Resource
源码:
/**
* 继承自InputStreamSource,即继承了getInputStream()
* 可以获取InputStreamSource类型的输入流
*/
public interface Resource extends InputStreamSource {
// 判断某个资源是否以物理形式存在
boolean exists();
// 判断资源是否可读,只有返回true了,才可以getInputStream()
default boolean isReadable() {
return exists();
}
// 判断资源是否打开,如果已经打开就不能重复多次读写,资源应该在读取完毕之后关闭
default boolean isOpen() {
return false;
}
// 判断资源是否为系统文件
default boolean isFile() {
return false;
}
// 获取资源对象的URL,不能表示为URL就抛异常
URL getURL() throws IOException;
// 获取资源对象的URI,不能表示为URI就抛异常
URI getURI() throws IOException;
// 获取资源的File表示对象,不能表示为File就抛异常
File getFile() throws IOException;
// 返回一个可以读取字节的通道
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
// 获取资源内容的长度
long contentLength() throws IOException;
// 获取资源最后修改的时间戳
long lastModified() throws IOException;
// 相对于当前资源创建新的资源对象
Resource createRelative(String relativePath) throws IOException;
// 获取资源的文件名
@Nullable
String getFilename();
// 获取资源的描述信息
String getDescription();
}
Resource
接口中定义的方法,只是通用型,并不是每一种实现类资源类型都必须实现所有方法,比如getFile()
方法是基于文件类型的资源实现类才需要实现,如果不是基于文件的资源一般不需要实现此方法。
- 可写的资源操作接口——
WritableResource
接口
由于Resource
接口继承自InputStreamSource
,而且并没有扩展资源的写操作,因为对于大部分资源来说,不一定是可写的,或者是不需要写操作,但一定是需要可读的。Spring针对需要进行写操作的资源单独定义了一个WritableResource
接口,其中定义了三个写操作相关的方法:
public interface WritableResource extends Resource {
// 表示资源是否支持写操作
default boolean isWritable() {
return true;
}
// 用来获取OutputStream输出流
OutputStream getOutputStream() throws IOException;
// 获取一个可以写入字节的通道
default WritableByteChannel writableChannel() throws IOException {
return Channels.newChannel(getOutputStream());
}
}
可写的资源一般也包含可读资源的各种特性,因此WritableResource
接口也继承了Resource
接口。
- 抽象公共实现类——
AbstractResource
抽象类
和其他接口体系一样,Spring在Resource
接口的基础上实现了一个通用的抽象公共类AbstractResource
,该抽象类实现了一些与资源类型无关的基础操作,如exists()
方法等。
在AbstractResource
的实现中,是默认认为资源不是以URL或者File形式表现的,这样就可以适应一些费文件体系的资源,如字节流体系的资源等,对这些类型的操作,一般只支持读取操作。子类还可以通过覆盖isOpen()
方法来标识当前资源是否支持多次读取。
由于AbstractResource只是简单实现了一些逻辑,这里就不展示源码了,本系列文章意在快速纵览全局,这里学习类在体系中的地位即可,可以自行去阅读源码。
- 具体实现的资源类型——
UrlResource
、FileSystemResource
、ClassPathResource
和ServletContextResource
。-
UrlResource
类:符合URL规范的资源类型都可以表示为UrlResource对象。其中URL既可以访问网络资源,也可以访问本地资源,加不同的前缀即可。 -
ClassPathResource
类:该资源类型在Spring中是非常常用的一种资源类型,用来访问类加载路径下的资源,相对于其他的Resource类型,该种类型在Web应用中可以自动搜索位于WEB-INF/classes下的资源文件,而无需使用绝对路径访问。 -
FileSystemResource
类:访问文件系统资源的资源和类型,可以访问本地操作系统的文件系统。和Java提供的Feil文件类访问文件系统作用接近,但FileSystemResource可以消除操作系统底层差异,对不同的操作系统使用同一的API来访问。FileSystemResource
类同时还实现了WritableResource
接口,支持对资源的写操作。 -
ServletContextResource
类:是ServletContext
资源的Resource
实现,用来访问相对于ServletContext路径下的资源。支持以流和URL的方式进行访问,但只有在扩展Web应用程序存档且资源实际位于文件系统上时才允许java.io.File访问。
-
- 处理资源编码的实现类——
EncodedResource
类
在Resource接口下,有一个特殊的实现类是EncodedResource
,该类主要实现了对文件编码类型的处理。
关于Reource资源接口体系的介绍就到这里,如果读者对其中某种资源类型比较感兴趣,可以去阅读Spring的源码,了解其具体的实现逻辑。
在这里Spring对于资源类的设计中,其实用到了策略模式的设计思想,资源的类型在运行时是动态变化的,Spring将这几种资源实现类的选择进行了策略封装,让资源可以根据用户传入的类型自动选择资源实现类。
这就是下面要讲的,Spring中自动根据资源地址,选择资源实现类的利器:ResourceLoader
接口体系。
ResourceLoader
接口体系
-
ResourceLoader
接口,顾名思义,设计的目的是为了快速返回(也就是加载)Resource实例的对象,其接口体系类图如下:
- 在上图的类图当中,我们可以看到几个熟悉的面孔——
ApplicationContext
,上下文容器。这说明高级容器(应用上下文容器)也是实现了ResourceLoader接口的,其本身就是一个ResourceLoader,也就是说高级容器都可以根据资源地址类型快速获取对应的Resource
实例。 - 资源获取策略的顶级接口——
ResourceLoader
接口
Resource主要提供了获取资源实例和ClassLoader的方法,其源码如下:
public interface ResourceLoader {
// 支持 classpath: 类型的路径匹配
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
// 根据路径信息返回一个对应的Resource资源实例
Resource getResource(String location);
// 获取当前使用的ClassLoader,对外暴露,让ResourceLoader可以使用自定义的ClassLoader
@Nullable
ClassLoader getClassLoader();
}
-
ResourceLoader
接口的默认实现类——DefaultResourceLoader
类
DefaultResourceLoader
类实现了ResourceLoader
接口加载资源的方法,同时也扩展了一些通用的基本操作方法,其中最重要的就是实现了getResource(String location);
方法,其实现逻辑源码如下:
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
// ProtocolResolver:用户自定义协议资源解决策略,看看用户是否有提前自定义
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
// 如果用户没有自定义策略,就用以下自定义资源解析策略
// 如果是"/"打头,就构造并返回ClassPathResource
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
// 以"classpath:"打头也会构造并返回ClassPathResource在构造资源时还会获取类加载器
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
// 如果上述方式无法构造。则构造URL地址,并尝试通过URL进行资源定位,若没有就抛出异常
// 然后判断是否为FileURL,如果是就返回FileUrlResource,否则就构造UrlResource
// 若是在加载的过程中抛出MalformedURLException,就委派getResourceByPath来实现资源定位和加载
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// 不符合URL规范就当做普通路径处理
return getResourceByPath(location);
}
}
}
-
ResourceLoader
接口的增强——ResourcePatternResolver
接口
Spring在提供了ResourceLoader
接口之后,还提供了一个对其进行了增强的ResourcePatternResolver
接口来扩展ResourceLoader
。除了继承自Resource的方法外,ResourcePatternResolver额外扩充了一个方法,用来批量加载Resource资源对象,其源码如下:
public interface ResourcePatternResolver extends ResourceLoader {
// 支持classpath*:形式路径匹配,即Ant风格
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
// 批量加载Resource资源类型的实现
Resource[] getResources(String locationPattern) throws IOException;
}
可以看出,ResourcePatternResolver
支持了"classpath*:"开头的资源路径形式,即Ant风格路径,允许使用通配符来对路径进行匹配。"classpath*:"可以返回路径下所有满足条件的资源实例,而ResourceLoader本身只能一次返回一个资源。
-
ResourcePatternResolver
接口的默认实现类——PathMatchingResourcePatternResolver
类
PathMatchingResourcePatternResolver
中实例化了一个DefaultResourceLoader
,继承来的ResourceLoader
中的方法,就委托给了内部的DefaultResourceLoader
对象去处理,而PathMatchingResourcePatternResolver
只负责处理实现ResourcePatternResolver
中的方法,主要是实现getResources(String locationPattern)方法。
PathMatchingResourcePatternResolver
中还引入了一个新的组件PathMatcher
,PathMatcher
负责对对基于字符串的路径和指定的模式符号进行匹配。
翻译一下这个接口的名字,可以将其翻译为路径匹配模板解释器,顾名思义,这个接口就是先用模板解释器对路径进行解析,分解成多个资源配置文件,将资源信息提供给资源加载器,后者根据不同策略将配置文件形成不同类型的资源(Resource)。
- 高级容器和ResourceLoader之间微妙的关系:实现了
ResourceLoader
接口的ApplicationContext
体系
关于高级容器的分析可以看这一篇:BeanFactory和ApplicationContext容器家族。
从ApplicationContext
的源码中,我们可以看到,这个接口确实继承了ResourcePatternResolver
接口,也就是ApplicationContext
本身也是个模板解释器和资源加载器,是模板解释器的具体实现,是支持Ant风格路径匹配和批量加载资源的一个资源加载器。
作为高级容器的顶级接口,ApplicationContext
继承了ResourcePatternResolver
接口,这也就代表了高级容器下面整个体系都间接地继承了ResourcePatternResolver
接口。
例如ApplicationContext
的抽象实现类AbstractApplicationContext
在实现ConfigurableApplicationContext
的基础上,同时还继承了ResourceLoader
的实现类DefaultResourceLoader
。
其实实现ConfigurableApplicationContext
也就意味着实现了ResourcePatternResolver
,但是Spring开发人员显然不想将资源解释的逻辑直接暴露在容器中,于是继承了DefaultResourceLoader
,将解析逻辑与容器的实现进行了解耦。
在AbstractApplicationContext
的构造函数源码中可以看到,在这里将ResourcePatternResolver
接口的实例——PathMatchingResourcePatternResolver
实例进行了初始化,并且传入的resourceLoader实例,就是容器本身(容器继承了DefaultResourceLoader
),也就是将容器进行了献祭,来实现资源解释器。
所以,Resource、ResourceLoader和容器之间的关系可以用下图来表示:
至此,Spring中Resource、ResourceLoader体系和作用已经讲解完毕,水平有限,有错误烦请指出。
- AngularJS 技术总结
- 《linux c编程指南》学习手记5
- AngularJS API之bootstrap启动
- 通过 JS 判断页面是否有滚动条的简单方法
- Log4j官方文档翻译(六、日志的级别)
- AngularJS API之isXXX()
- 《linux c编程指南》学习手记4
- Kibana中doc与search策略的区别
- jQuery 图片查看插件 Magnify 开发简介(仿 Windows 照片查看器)
- Log4j官方文档翻译(五、日志输出的方法)
- AngularJS API之copy深拷贝
- 光标定位,隐藏光标
- AngularJS API之toJson 对象转为JSON
- Log4j官方文档翻译(七、日志格式化)
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Android:Tools命名空间原来是有大用处的
- Android:无线调试就是这么简单
- Java自动化测试(回写与断言 17)
- 一篇就够——Kotlin快速入门
- 微信大牛教你深入了解数据库索引
- SqlServer 资源消耗查询
- Android:检查通知权限并跳转到通知设置界面
- OpenShift应用发布和运维设计
- Android:依赖Module的问题汇总
- Android:加载网图时精确获取图片格式
- Android:8.0中未知来源安装权限变更
- Android:RippleDrawable 水波纹/涟漪效果
- Android:Chip、ChipGroups、ChipDrawable
- Android9.0 使用 AndroidVideoCache 时不能缓存/播放视频的解决
- Android:流式布局实现总结