1. 简介
在JDK中 java.net.URL
适用于加载资源的类,但是 URL
的实现类都是访问网络资源的,并没有可以从类路径或者相对路径获取文件及 ServletContext
, 虽然可以通过自定义扩展URL接口来实现新的处理程序,但是这是非常复杂的,同时 URL
接口中定义的行为也并不是很理想的 ,如检测资源的存在等的行为,这也是 spring
为什么自己全新的开发一套自己的资源加载策略, 同时它也满足下面的特点:
- 单一职责原则,将资源的定义和资源的加载的模型界限划的非常清晰
- 采用高度抽象,统一的资源定义和资源加载的策略和行为,资源加载返回给客户端的是抽象的资源,客户端根据资源的行为定义对其进行具体化的处理
2. Resource 接口
spring
中的 Resource
接口目的在于成为一种功能更加强大的接口,用于抽象化对具体资源的访问,它继承了 org.springframework.core.io.InputStreamSource
接口,作为资源定义的顶级接口, Resource
内部定义了通用的方法,并且有它的子类 AbstractResource
来提供统一的默认实现,
Resouerce
接口定义:
//资源定义接口
public interface Resource extends InputStreamSource {
/**
* 检验资源是否是物理存在
*/
boolean exists();
/**
* 判断资源是否是可读的
*/
default boolean isReadable() {
return exists();
}
/**
* 判断资源是否是打开的,true为打开
*/
default boolean isOpen() {
return false;
}
/**
* 判断该资源是否是文件 true为是
*/
default boolean isFile() {
return false;
}
/**
* 返回该资源的URL句柄
*/
URL getURL() throws IOException;
/**
* 返回该资源的URI句柄
*/
URI getURI() throws IOException;
/**
* 获取该资源的File句柄
*/
File getFile() throws IOException;
/**
* 返回一个ReadableByteChannel 作为NIO中的可读通道
*/
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();
}
InputStreamSource
接口定义:
public interface InputStreamSource {
/**
* Return an {@link InputStream} for the content of an underlying resource.
* <p>It is expected that each call creates a <i>fresh</i> stream.
* <p>This requirement is particularly important when you consider an API such
* as JavaMail, which needs to be able to read the stream multiple times when
* creating mail attachments. For such a use case, it is <i>required</i>
* that each {@code getInputStream()} call returns a fresh stream.
* @return the input stream for the underlying resource (must not be {@code null})
* @throws java.io.FileNotFoundException if the underlying resource doesn't exist
* @throws IOException if the content stream could not be opened
*/
InputStream getInputStream() throws IOException;
}
该 Resource
中一些最重要的方法:
getInputStream()
:找到并打开资源,并返回一个资源以InputStream
供读取,每次调用都会返回一个新的InputStream
,调用者有责任关闭流exists()
:返回boolean
指示此资源是否实际以物理形式存在。isOpen()
:返回,boolean
指示此资源是否表示具有打开流的句柄, 如果为true
,InputStream
则不能多次读取,必须只读取一次,然后将其关闭以避免资源泄漏。返回false
所有常用资源实现(除外)InputStreamResource
可读getDescription()
:返回对此资源的描述,以便在使用该资源时用于错误输出。这通常是标准文件名或资源的实际URL
**
Resource
实现**
UrlResource
: 包装一个java.net.URL
,可用于访问通常可以通过URL
访问的任何对象,例如文件,HTTP
目标,FTP
目标等。所有URL
都有一个标准化的String
表示形式,因此适当的标准化前缀可用于指示另一种URL
类型。如:file
: 访问文件系统路径,http
: 通过HTTP
协议ftp
: 访问资源,通过FTP
访问资源等ClassPathResource
: 此类表示应从类路径获取的资源。它使用线程上下文类加载器(ClassLoader
),给定的类加载器或给定的类来加载资源FileSystemResource
: 是一个Resource
执行java.io.File
和java.nio.file.Path
类型资源的封装,它支持File
和URL
, 实现WritableResource
接口,且从Spring Framework 5.0
开始,FileSystemResource
使用NIO2 API
进行读/写交互ServletContextResource
: 该ServletContex
t资源解释相关Web
应用程序的根目录内的相对路径。InputStreamResource
: 将给定的 InputStream 作为一种资源的 Resource 的实现类ByteArrayResource
: 这是Resource给定字节数组的实现。它为给定的字节数组创建一个ByteArrayInputStream
3. ResourceLoader 接口
ResourceLoader
主要是用于返回(即加载) Resource
对象,主要定义:
public interface ResourceLoader {
/** Pseudo URL prefix for loading from the class path: "classpath:". */
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
/**
* 返回指定路径的资源处理器
* 必须支持完全限定的网址: "file:C:/test.dat"
* 必须支持ClassPath 的 URL :"classpath:test.dat"
* 必须支持相对路径 : "WEB-INF/test.dat"
* 并不能保证资源是否物理存在,需要自己去检测通过existence
* 再spring中所有的应用上下文都去实现这个接口,可以进行资源的加载
*/
Resource getResource(String location);
/**
* 返回当前类的 ClassLoader 对象
*/
@Nullable
ClassLoader getClassLoader();
}
应用上下文即容器都有实现
ResourceLoader
这个接口,所有的上下文都可以用于获取Resource
实例对象我们可以在特定的应用上下文中通过
getResource()
来获取特定类型的Resource
实例,但是的保证location
路径没有特殊的前缀,如classpatch:
等,如果有特定前缀慢么会强制使用相应的资源类型,与上下文无关。
Prefix | Example | Explanation |
---|---|---|
classpath: | classpath:com/myapp/config.xml | 从类路径加载 |
file: | file:///data/config.xml | 从文件系统作为 URL 加载 |
http: | https://myserver/logo.png | 按照URL形式加载 |
(none) | /data/config.xml | 取决于应用上下文 |
ResourceLoader
的子类结构:
3.1 DefaultResourceLoader
这个类是 ResourceLoader
的默认实现类,与 Resource
接口的 AbstractResource
一样,
3.1.1. 构造函数
- 提供有参和无参的构造函数,有参构造函数接受
ClassLoader
类型,如不带参数则使用默认的ClassLoader
,Thread.currentThread()#getContextClassLoader()
核心代码代码,部分省去:
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);
/**
* 无参构造函数
* @see java.lang.Thread#getContextClassLoader()
*/
public DefaultResourceLoader() {
this.classLoader = ClassUtils.getDefaultClassLoader();
}
/**
* 带ClassLoader的有参构造函数
*/
public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* 设置 ClassLoader
*/
public void setClassLoader(@Nullable ClassLoader classLoader) {
this.classLoader = classLoader;