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
接口的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体系和作用已经讲解完毕,水平有限,有错误烦请指出。