public interface Resource extends InputStreamSource {/ 资源是否存在 /boolean exists();/ 资源是否可读 /default boolean isReadable() {return exists();}/ 指示此资源是否表示具有开放流的句柄。如果为true,则不能多次读取InputStream, 必须读取并关闭InputStream以避免资源泄露。对付范例的资源描述符,将为false。 /default boolean isOpen() {return false;}/ 是否 File /default boolean isFile() {return false;}/ 返回资源的 URL /URL getURL() throws IOException;/ 返回资源的 URI /URI getURI() throws IOException;/ 返回资源的 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;/ 确定此资源的文件名,即常日路径的末了一部分:例如“myfile.txt”。如果此类型的资源没有文件名,则返回null /@NullableString getFilename();/ 资源的描述 /String getDescription();
1.2 Resource干系的类图
从上面类图可以看到,Resource 根据资源的不同类型供应不同的详细实现,如下:
AbstractResource是个抽象类,为 Resource 接口的默认抽象实现。它实现了 Resource 接口的大部分的公共实现,是Resource接口最主要的实现,源码如下:

public abstract class AbstractResource implements Resource {/ 此实现检讨文件是否可以打开,若判断过程产生缺点或者非常,就关闭对应的流 /@Overridepublic boolean exists() {// 基于 File 文件系统进行判断if (isFile()) {try {return getFile().exists();}catch (IOException ex) {Log logger = LogFactory.getLog(getClass());if (logger.isDebugEnabled()) {logger.debug("Could not retrieve File for existence check of " + getDescription(), ex);}}}//若判断过程产生缺点或者非常,就关闭对应的流try {getInputStream().close();return true;}catch (Throwable ex) {Log logger = LogFactory.getLog(getClass());if (logger.isDebugEnabled()) {logger.debug("Could not retrieve InputStream for existence check of " + getDescription(), ex);}return false;}}/ 同exists()方法同等 /@Overridepublic boolean isReadable() {return exists();}/ 直接返回 false,表明没有打开 /@Overridepublic boolean isOpen() {return false;}/ 直接返回false,表明不是 File /@Overridepublic boolean isFile() {return false;}/ 获取URL,直接抛出非常 /@Overridepublic URL getURL() throws IOException {throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");}/ 基于 getURL() 返回的 URL 构建 URI /@Override@SuppressWarnings("deprecation")public URI getURI() throws IOException {URL url = getURL();try {return ResourceUtils.toURI(url);}catch (URISyntaxException ex) {throw new org.springframework.core.NestedIOException("Invalid URI [" + url + "]", ex);}}/ 获取File,直接抛出非常 /@Overridepublic File getFile() throws IOException {throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");}/ 根据 getInputStream() 的返回结果构建 ReadableByteChannel /@Overridepublic ReadableByteChannel readableChannel() throws IOException {return Channels.newChannel(getInputStream());}/ 获取资源的长度,实际便是资源内容的字节长度,通过全部读取一遍来判断 /@Overridepublic long contentLength() throws IOException {InputStream is = getInputStream();try {long size = 0;byte[] buf = new byte[256];int read;while ((read = is.read(buf)) != -1) {size += read;}return size;}finally {try {is.close();}catch (IOException ex) {Log logger = LogFactory.getLog(getClass());if (logger.isDebugEnabled()) {logger.debug("Could not close content-length InputStream for " + getDescription(), ex);}}}}/ 返回资源末了的修正韶光 /@Overridepublic 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();}/ 根据相对路径创建资源,直接抛出非常 /@Overridepublic Resource createRelative(String relativePath) throws IOException {throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());}/ 获取文件名称,直接返回null /@Override@Nullablepublic String getFilename() {return null;}@Overridepublic boolean equals(@Nullable Object other) {return (this == other || (other instanceof Resource &&((Resource) other).getDescription().equals(getDescription())));}@Overridepublic int hashCode() {return getDescription().hashCode();}/ 返回资源的描述 /@Overridepublic String toString() {return getDescription();}}
通过源码我们可以看到,AbstractResource实现了较大部分的Resource接口的内容,并且将一些非通用的实现交给子类去实现。以是如果我们想要实现自定义的 Resource ,记住不要实现 Resource 接口,而该当继续 AbstractResource 抽象类,然后根据当前的详细资源特性覆盖相应的方法即可。
1.4 AbstractResource的其他子类从类图上我们可以看到,FileSystemResource、InputStreamResource、ByteArrayResource、ClassPathResource、UrlResource这些都是AbstractResource的子类。根据名称,我们基本就能猜出各个子类所代表的资源类型,干系的源码这里不再逐一进行剖析了。
2. Spring Framework中的资源加载器ResourceLoaderSpring Framework将资源的定义和资源的加载区分开了,Resource 定义了统一的资源,而资源的加载则由 ResourceLoader 来统一定义。ResourceLoader 紧张用于根据给定的资源文件地址,返回对应的 Resource 。
2.1 ResourceLoader的源码ResourceLoader接口在org.springframework.core.io包下,源码如下:
public interface ResourceLoader {/ CLASSPATH URL 前缀。默认为:"classpath:"/String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;/ 根据资源路径返回资源工具,它不确保该 Resource 一定存在,须要调用 Resource#exist() 方法来判断。 该方法支持以下模式的资源加载: URL位置资源,如 "file:C:/test.dat" 。 ClassPath位置资源,如 "classpath:test.dat 。 相对路径资源,如 "WEB-INF/test.dat" 返回的Resource 实例,根据实现不同而不同。 紧张实现是在子类 DefaultResourceLoader 中实现,详细过程我们在剖析 DefaultResourceLoader 时做详细解释。 /Resource getResource(String location);/返回 ClassLoader 实例,对付想要获取 ResourceLoader 利用的 ClassLoader 用户来说,可以直接调用该方法来获取。在剖析 Resource 时,提到了一个类 ClassPathResource ,这个类是可以根据指定的 ClassLoader 来加载资源的。/@NullableClassLoader getClassLoader();}
2.2 ResourceLoader的类图
DefaultResourceLoader是ResourceLoader的默认实现类,它实现了 ResourceLoader接口的大部分的公共实现,是ResourceLoader接口最主要实现类,源码如下:
public class DefaultResourceLoader implements ResourceLoader {@Nullableprivate ClassLoader classLoader;private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);/ 无参布局函数 /public DefaultResourceLoader() {}/ 带 ClassLoader 参数的布局函数 /public DefaultResourceLoader(@Nullable ClassLoader classLoader) {this.classLoader = classLoader;}/ 设置ClassLoader /public void setClassLoader(@Nullable ClassLoader classLoader) {this.classLoader = classLoader;}/ 获取ClassLoader /@Override@Nullablepublic ClassLoader getClassLoader() {return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());}/ 添加自定义的协议解析器 /public void addProtocolResolver(ProtocolResolver resolver) {Assert.notNull(resolver, "ProtocolResolver must not be null");this.protocolResolvers.add(resolver);}/ 返回自定义解析器的凑集 /public Collection<ProtocolResolver> getProtocolResolvers() {return this.protocolResolvers;}@SuppressWarnings("unchecked")public <T> Map<Resource, T> getResourceCache(Class<T> valueType) {return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());}public void clearResourceCaches() {this.resourceCaches.clear();}/ 最核心的方法,获取资源工具。/@Overridepublic Resource getResource(String location) {Assert.notNull(location, "Location must not be null");// 1、通过 自定义的ProtocolResolvers来加载资源for (ProtocolResolver protocolResolver : getProtocolResolvers()) {Resource resource = protocolResolver.resolve(location, this);if (resource != null) {return resource;}}// 2、如果通过自定义的ProtocolResolvers没有得到资源,就考试测验 以 / 开头,返回 ClassPathContextResource //类型的资源if (location.startsWith("/")) {return getResourceByPath(location);}//3.考试测验以 classpath: 开头,返回 ClassPathResource 类型的资源else if (location.startsWith(CLASSPATH_URL_PREFIX)) {return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());}else {try {//末了.根据是否为文件 URL ,是则返回 FileUrlResource 类型的资源,否则返回 UrlResource 类型的资源URL url = new URL(location);return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));}catch (MalformedURLException ex) {// 末了,如果涌现非常,返回 ClassPathContextResource 类型的资源return getResourceByPath(location);}}}protected Resource getResourceByPath(String path) {return new ClassPathContextResource(path, getClassLoader());}protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {super(path, classLoader);}@Overridepublic String getPathWithinContext() {return getPath();}@Overridepublic Resource createRelative(String relativePath) {String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);return new ClassPathContextResource(pathToUse, getClassLoader());}}}
2.4 ProtocolResolver的源码
在2.3 DefaultResourceLoader源码中我们看到,getResource这个方法中,首先利用的是自定义的协议解析器来获取资源工具。以是如果我们要实现自己的资源获取办法,可以不须要继续DefaultResourceLoader,可以通过实现ProtocolResolver接口来获取Resource资源工具。ProtocolResolver接口的源码如下:
@FunctionalInterfacepublic interface ProtocolResolver {/ Resolve the given location against the given resource loader if this implementation's protocol matches. @param location the user-specified resource location 资源路径 @param resourceLoader the associated resource loader 指定的加载器 ResourceLoader @return a corresponding {@code Resource} handle if the given location matches this resolver's protocol, or {@code null} otherwise 返回为相应的 Resource /@NullableResource resolve(String location, ResourceLoader resourceLoader);}
这个接口比较大略,就一个resolve方法。如果我们自定义一个ProtocolResolver实现后,直接调用 DefaultResourceLoader#addProtocolResolver(ProtocolResolver) 方法即可。
/ 自定义的协议解析器的凑集 /private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);/ 添加自定义的协议解析器 /public void addProtocolResolver(ProtocolResolver resolver) {Assert.notNull(resolver, "ProtocolResolver must not be null");this.protocolResolvers.add(resolver);}
2.5 DefaultResourceLoader的其他子类
从类图上我们可以看到,FileSystemResourceLoader、ClassRelativeResourceLoader这些都是DefaultResourceLoader的子类。根据名称,我们基本就能猜出各个子类所代表的加载器类型。干系的源码这里不再逐一进行剖析了。
3.小结Spring Framework全体资源加载过程就已经基本剖析完成了。
Resource 和 ResourceLoader 来统一抽象全体资源及其加载办法。使得资源与资源的定位有了一个更加清晰的界线,并且供应了得当的 Default 类,使得自定义实现更加方便和清晰。AbstractResource 为 Resource 的默认抽象实现类,它对 Resource 接口做了一个统一的实现,子类继续该类后只须要覆盖相应的方法即可,同时对付自定义的 Resource 我们也是继续该类。DefaultResourceLoader 同样也是 ResourceLoader 的默认实现,在自定 ResourceLoader 的时候我们除了可以继续该类外还可以实现 ProtocolResolver 接口来实现自定资源加载协议。