1.统一资源接口的子类结构
WritableResource
: 可写资源接口ByteArrayResource
:二进制数组资源ClassPathResource
:类路径下的资源,资源以相对路径的方式表示FileSystemResource
:文件系统资源,资源以文件系统的方式表示如:D://data/beans.xmlServletContextPathResource
:为访问Web容器上下文中的资源而设计的类,负责以相对于Web应用根目录的路径加载资源。它支持以流和URL的方式访问,在War解包的情况下,也可以通过File方式访问,。该类可以直接从JAR包中访问资源。PathResource
:Path封装了java.net.URL
、java.nio.file.Path
、文件系统资源,它可以访问任何通过URL、Path、文件系统路径表示的资源,如文件系统资源、HTTP资源等。
2.统一资源Resource
Spring框架内使用的org.springframework.core.io.Resouce
接口作为所有资源的抽象和访问接口。
public interface Resource extends InputStreamSource {
/**
* 判断资源文件是否存在
*/
boolean exists();
/**
* 资源文件是否可读
*/
default boolean isReadable() {
return exists();
}
/**
* 判断资源文件句柄是否被打开
*/
default boolean isOpen() {
return false;
}
/**
* 判定资源文件是否为File
*/
default boolean isFile() {
return false;
}
/**
* 返回资源文件的URL
*/
URL getURL() throws IOException;
/**
* 返回资源文件URI
*/
URI getURI() throws IOException;
/**
* 返回资源文件对象
*/
File getFile() throws IOException;
/**
* 返回 ReadableByteChannel nio channel访问
*/
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
/**
* 资源文件内容长度
*/
long contentLength() throws IOException;
/**
* 资源文件的上次修改时间
*/
long lastModified() throws IOException;
/**
*根据资源we年的相对路径创建新资源
*/
Resource createRelative(String relativePath) throws IOException;
/**
* 获取文件名称
*/
@Nullable
String getFilename();
/**
* 资源描述
*/
String getDescription();
}
Resource
作为资源的统一抽象,定义了一些通用的方法,由子类AbstractResource
提供了统一的默认实现,如果需要自定义拓展,可以直接继承此类。
public abstract class AbstractResource implements Resource {
/**
* 判断资源文件是否存在
*/
@Override
public boolean exists() {
try {
// 通过File文件方法判断文件是否存在
return getFile().exists();
}
catch (IOException ex) {
try {
// 基于InputStream判断文件是否存在
getInputStream().close();
return true;
}
catch (Throwable isEx) {
return false;
}
}
}
/**
* 资源文件是否可读
*/
@Override
public boolean isReadable() {
return exists();
}
/**
* 直接返回false 表示未打开
*/
@Override
public boolean isOpen() {
return false;
}
/**
* 直接返回false,表示不为File
*/
@Override
public boolean isFile() {
return false;
}
/**
* 直接抛出异常,由子类自定义覆盖实现
*/
@Override
public URL getURL() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
}
/**
* 基于getURL() 构建URI
*/
@Override
public URI getURI() throws IOException {
URL url = getURL();
try {
return ResourceUtils.toURI(url);
}
catch (URISyntaxException ex) {
throw new NestedIOException("Invalid URI [" + url + "]", ex);
}
}
/**
* 直接抛出异常,由子类实现
*/
@Override
public File getFile() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
}
/**
* 根据InputStream构建ByteChannel
*/
@Override
public ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
/**
* 计算资源文件字节长度
*/
@Override
public 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) {
}
}
}
/**
* 获取文件最后一次修改时间 timestamp
*/
@Override
public 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;
}
/**
* 获取资源File(拿到要获取最后修改时间的资源File)
*/
protected File getFileForLastModifiedCheck() throws IOException {
return getFile();
}
/**
* 根据相对路径构造Resource,默认实现抛出 FileNotFoundException 异常,交个子类实现
*/
@Override
public Resource createRelative(String relativePath) throws IOException {
throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
}
/**
* 获取资源名称,默认返回null,交给子类实现
*/
@Override
@Nullable
public String getFilename() {
return null;
}
/**
* This implementation compares description strings.
* @see #getDescription()
*/
@Override
public boolean equals(Object other) {
return (this == other || (other instanceof Resource &&
((Resource) other).getDescription().equals(getDescription())));
}
/**
* This implementation returns the description's hash code.
* @see #getDescription()
*/
@Override
public int hashCode() {
return getDescription().hashCode();
}
/**
* This implementation returns the description of this resource.
* @see #getDescription()
* 返回资源描述
*/
@Override
public String toString() {
return getDescription();
}
}
3.资源加载器ResourceLoader结构体系
ResourcePatternResolver
:是对ResourceLoader的拓展,根据资源文件location一次加载多个资源文件,返回一个Resource列表。PathMatchingResourcePatternResolver
: 是ResourcePatternResolver
的最常用的子类,除了支持"classpath*:"
前缀外,还支持Ant锋哥的路径匹配模式(如:**/*.xml)。FileSystemResourceLoader
:继承了DefaultResourceLoader
并覆写了getResourceByPath()
方法。该类主要增强了可以从文件系统加载资源并返回FileSystemResource
。ClassRelativeResourceLoader
:是类似于FileSystemResourceLoader
,是DefaultResourceLoader
的另一个子类 ,也是覆写了getResourceByPath()
。该类主要增强了可以根据给定的Class所在的包或者包的子包下记载资源,返回的是ClassPathResource
。
4.源码分析
4.1 资源定位:ResourceLoader
Spring将资源的定义和资源的加载区分开来,Resource定义了统一的资源,那资源加载则由ResourceLoader来统一加载。
public interface ResourceLoader {
/** Pseudo URL prefix for loading from the class path: "classpath:". */
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
/**
* Return a Resource handle for the specified resource location.
* 通过资源定位返回资源文件
* <p>The handle should always be a reusable resource descriptor,
* allowing for multiple {@link Resource#getInputStream()} calls.
* <p><ul>
* <li>Must support fully qualified URLs, e.g. "file:C:/test.dat".
* <li>Must support classpath pseudo-URLs, e.g. "classpath:test.dat".
* <li>Should support relative file paths, e.g. "WEB-INF/test.dat".
* (This will be implementation-specific, typically provided by an
* ApplicationContext implementation.)
* </ul>
* <p>Note that a Resource handle does not imply an existing resource;
* you need to invoke {@link Resource#exists} to check for existence.
* @param location the resource location
* @return a corresponding Resource handle (never {@code null})
* @see #CLASSPATH_URL_PREFIX
* @see Resource#exists()
* @see Resource#getInputStream()
*/
Resource getResource(String location);
/**
* Expose the ClassLoader used by this ResourceLoader.
* 返回资源加载器的类加载器
* <p>Clients which need to access the ClassLoader directly can do so
* in a uniform manner with the ResourceLoader, rather than relying
* on the thread context ClassLoader.
* @return the ClassLoader
* (only {@code null} if even the system ClassLoader isn't accessible)
* @see org.springframework.util.ClassUtils#getDefaultClassLoader()
* @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
*/
@Nullable
ClassLoader getClassLoader();
}
ResourceLoader为Spring定义的统一资源加载接口。
4.2 资源加载器的默认实现:DefaultResourceLoader
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);
/**
* 无参构造函数
* <p>ClassLoader access will happen using the thread context class loader
*/
public DefaultResourceLoader() {
// 默认使用thread context class loader
this.classLoader = ClassUtils.getDefaultClassLoader();
}
/**
* 可以手动设置类加载器
* @param classLoader the ClassLoader to load class path resources with, or {@code null}
* for using the thread context class loader at the time of actual resource access
*/
public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Specify the ClassLoader to load class path resources with, or {@code null}
* for using the thread context class loader at the time of actual resource access.
* <p>The default is that ClassLoader access will happen using the thread context
* class loader at the time of this ResourceLoader's initialization.
*/
public void setClassLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Return the ClassLoader to load class path resources with.
*/
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
/**
* 这里是设置用自定义的资源加载协议,是DefaultResourceLoaderde的SPI
* 允许用户自定义加载协议美不需要集成ResourceLoader的子类
* 用户自定义加载资源协议需要自定义实现ProtocolResolver接口,实现resolve方法,然后在此处添加到加载协议
* Register the given resolver with this resource loader, allowing for
* additional protocols to be handled.
* <p>Any such resolver will be invoked ahead of this loader's standard
* resolution rules. It may therefore also override any default rules.
* @since 4.3
* @see #getProtocolResolvers()
*/
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.add(resolver);
}
/**
* 返回自定义的加载协议集合
* Return the collection of currently registered protocol resolvers,
* allowing for introspection as well as modification.
* @since 4.3
*/
public Collection<ProtocolResolver> getProtocolResolvers() {
return this.protocolResolvers;
}
/**
* Obtain a cache for the given value type, keyed by {@link Resource}.
* @param valueType the value type, e.g. an ASM {@code MetadataReader}
* @return the cache {@link Map}, shared at the {@code ResourceLoader} level
* @since 5.0
*/
@SuppressWarnings(<