Resource
在Spring中,对资源进行了抽象,从而屏蔽了资源类型和来源的区别,使得内部对于操作这些资源的API更加统一。下图为Spring中Resource
的继承体系图:
根据继承关系图可以看到,整个Resource
体系中,进行了一定层级的抽象,通过顶层的借口定义资源常见的操作,然后给出一个通用的抽象实现,最后各个不同类型的资源各自实现各自特殊的处理方法。
最顶层的Resource
接口继承自InputStreamSource
接口,而InputStreamSource
接口中仅定义了一个方法:getInputStream
。凡是可以获取输入流的资源都可以是一个InputStreamSource
类型。而对于大多数资源文件来说,不一定可写但一般是可读的。因此这里专门为可写的资源类型定义了WritableResource
接口,此接口中定义了两个和写操作相关的方法:
- isWritable
- getOutputStream
可写的资源一般也包含可读资源的各种特性,因此WritableResource
接口继承自Resource
接口。在Resource
接口中定义了一些通用的方法:
public interface Resource extends InputStreamSource {
// 判断资源是否存在
boolean exists();
// 判断资源是否可读,只有在返回true的时候,getInputStream方法才可用
boolean isReadable();
// 判断资源是否已打开,如果已打开则资源不能多次读写,资源应该在读完成之后关闭。
boolean isOpen();
// 获取资源对象的URL,如果该资源不能表示为URL形式则抛出异常
URL getURL() throws IOException;
// 获取资源对象的URI,如果该资源不能表示为URI形式则抛出异常
URI getURI() throws IOException;
// 获取资源的File表示对象,如果资源不能表示为File对象则抛出异常
File getFile() throws IOException;
// 获取资源内容的长度,如果资源无法解析则抛出异常
long contentLength() throws IOException;
// 获取资源最后修改时间戳,如果资源无法解析则抛出异常
long lastModified() throws IOException;
// 相对当前资源创建新的资源对象,如果相对的资源无法解析则抛出异常
Resource createRelative(String relativePath) throws IOException;
// 获取当前资源的文件名,如果当前资源没有文件名则返回null
String getFilename();
// 获取当对资源的描述信息
String getDescription();
}
在Resource
接口中定义的方法,并不需要每一种实际资源类型都必须实现,各个实际资源类型根据自身的情况决定要实现哪些方法。例如基于文件的资源一般会实现getFile
方法,而不是基于文件的资源则一般不实现getFile
方法。
在具体资源实现之前,Spring还提供了一个抽象的公共实现AbstractResource
,在AbstractResource
中实现一些跟资源类型无关的通用逻辑,和底层资源有关的则留给具体的资源类型去实现。
public abstract class AbstractResource implements Resource {
public boolean exists() {
// Try file existence: can we find the file in the file system?
try {
return getFile().exists();
} catch (IOException ex) {
// Fall back to stream existence: can we open the stream?
try {
InputStream is = getInputStream();
is.close();
return true;
} catch (Throwable isEx) {
return false;
}
}
}
public boolean isReadable() {
return true;
}
public boolean isOpen() {
return false;
}
public URL getURL() throws IOException {
// 默认认为资源无法表示为URL,子类可覆写此方法
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
}
public URI getURI() throws IOException {
URL url = getURL();
try {
// 通过getURL方法的返回值来进行转换
return ResourceUtils.toURI(url);
} catch (URISyntaxException ex) {
throw new NestedIOException("Invalid URI [" + url + "]", ex);
}
}
public File getFile() throws IOException {
// 默认认为资源无法表示为File对象,子类可覆写
throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
}
public long contentLength() throws IOException {
InputStream is = this.getInputStream();
Assert.state(is != null, "resource input stream must not be null");
try {
// 默认实现为读取inputStream中的所有数据来获取长度
long size = 0;
byte[] buf = new byte[255];
int read;
while((read = is.read(buf)) != -1) {
size += read;
}
return size;
} finally {
try {
is.close();
} catch (IOException ex) {
}
}
}
public long lastModified() throws IOException {
long lastModified = getFileForLastModifiedCheck().lastModified();
if (lastModified == 0L) {
throw