这篇文章主要介绍“怎么理解Spring中的Resource与ResourceLoader体系”,在日常操作中,相信很多人在怎么理解Spring中的Resource与ResourceLoader体系问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么理解Spring中的Resource与ResourceLoader体系”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
这些Resources主要就分为2大类:只读,与可读可写。
从上面类图我们可以看出FileSystemResource,实现了WritableResource,因此仅有这个类属于可读可写,而其它的均属于只读的Resource.
Resources接口
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方法。
对于大多数资源文件来说,不一定可写但一般是可读的。因此这里专门为可写的资源类型定义了WritableResource接口,此接口中定义了两个和写操作相关的方法:
boolean isWritable();
OutputStream getOutputStream() throws IOException;
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 new FileNotFoundException(getDescription() +
" cannot be resolved in the file system for resolving its last-modified timestamp");
}
return lastModified;
}
protected File getFileForLastModifiedCheck() throws IOException {
return getFile();
}
public Resource createRelative(String relativePath) throws IOException {
// 默认不支持创建相对路径资源
throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
}
public String getFilename() {
return null; // 默认返回null,即认为资源五文件名
}
@Override
public String toString() {
return getDescription();
}
@Override
public boolean equals(Object obj) {
return (obj == this ||
(obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription())));
}
@Override
public int hashCode() {
return getDescription().hashCode();
}
}
在AbstractResource的实现中,默认认为资源不能够表示为URL和File的形式,这样的资源如ByteArrayResource、InputStreamResource等都可以适应,因为这些资源类型底层并不是基于文件而是包装了字节数组或输入流而成,因此正对这种类型的操作,一般只支持读取操作。
从上面的继承关系图中可以看到,FileSystemResource不但继承了AbstractResource还实现了WritableResource接口,也就是基于文件系统的资源类型,一般可以支持读写操作,当然一般也会支持相对路径资源。
abstractFileResolvingResource表示需要通过解析URL来获取的资源。例如其exists方法的实现:
@Override
public boolean exists() {
try {
URL url = getURL();
if (ResourceUtils.isFileURL(url)) {
// Proceed with file system resolution
return getFile().exists();// 如果是文件,则直接检测文件是否存在
}
else {
// Try a URL connection content-length header
URLConnection con = url.openConnection();
customizeConnection(con);
HttpURLConnection httpCon =
(con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
if (httpCon != null) {
// 如果是http url则检测url对应的资源是否存在
int code = httpCon.getResponseCode();
if (code == HttpURLConnection.HTTP_OK) {
return true;
}
else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
return false;
}
}
if (con.getContentLength() >= 0) {
return true;
}
if (httpCon != null) {
// No HTTP OK status, and no content-length header: give up
httpCon.disconnect();
return false;
}
else {
// Fall back to stream existence: can we open the stream?
getInputStream().close();
return true;
}
}
}
catch (IOException ex) {
return false;
}
}
并且他还有两个子类:UrlResource和ClassPathResouce,其中ClassPathResource类型是我们在Spring中非常常用的资源类型。
UrlResource来说,基本上就是通过解析URL来完成相关的操作,只要符合URL规范的格式都可以表示为UrlResource对象。
public class ClassPathResource extends AbstractFileResolvingResource {
private final String path;
private ClassLoader classLoader;
private Class<?> clazz;
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
public ClassPathResource(String path, ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
String pathToUse = StringUtils.cleanPath(path);
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
// 如果ClassLoader为null则使用默认的ClassLoader
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
public ClassPathResource(String path, Class<?> clazz) {
Assert.notNull(path, "Path must not be null");
this.path = StringUtils.cleanPath(path);
this.clazz = clazz;// 使用Class来加载资源,也可以使用ClassLoader加载
}
protected ClassPathResource(String path, ClassLoader classLoader, Class<?> clazz) {
this.path = StringUtils.cleanPath(path);
// 同时使用Clss和ClassLoader
this.classLoader = classLoader;
this.clazz = clazz;
}
public final String getPath() {
return this.path;
}
public final ClassLoader getClassLoader() {
return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
}
@Override
public boolean exists() {
return (resolveURL() != null);
}
protected URL resolveURL() {
// 资源是否存在通过Class和ClassLoader来判断
if (this.clazz != null) {
return this.clazz.getResource(this.path);
}
else if (this.classLoader != null) {
return this.classLoader.getResource(this.path);
}
else {
return ClassLoader.getSystemResource(this.path);
}
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
// InputStream的获取也是通过Class和ClassLoader来判断
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
@Override
public URL getURL() throws IOException {
URL url = resolveURL();
if (url == null) {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
}
return url;
}
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) :
new ClassPathResource(pathToUse, this.classLoader));
}
@Override
public String getFilename() {
return StringUtils.getFilename(this.path);
}
@Override
public String getDescription() {
StringBuilder builder = new StringBuilder("class path resource [");
String pathToUse = path;
if (this.clazz != null && !pathToUse.startsWith("/")) {
builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
builder.append('/');
}
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
builder.append(pathToUse);
builder.append(']');
return builder.toString();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof ClassPathResource) {
ClassPathResource otherRes = (ClassPathResource) obj;
return (this.path.equals(otherRes.path) &&
ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&
ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));
}
return false;
}
@Override
public int hashCode() {
return this.path.hashCode();
}
}
虽然ClassPathContextResource和ClassPathRelaticeContextResource都是继承自ClassPathResource,但是前者使用的ClassLoader来加载资源,后者使用的是Class来加载资源,二者还是有区别的:
ClassLoader来加载资源,路径以类路径的根目录为基准(如WEB-INF/classes为基准)。
Class来架子资源,资源以Class缩在的包路径为基准(如Web-INF/classes/com/test/resource/)
Spring中对资源进行抽象,从而统一对资源操作的API,屏蔽不同资源之间的差异。使得其他组件可以不关心具体资源类型的实现,使用统一的API进行操作,并且通过不同的接口来分别定义资源的不同行为,然后通过抽象类的形式给出一个通用的实现,底层具体的实现只需要继承这个抽象类,并覆写跟当前资源类型需要特殊处理的方法即可。另外,在定义接口时,通过给出一对方法(如:isReadable和getInputStream)来分离条件检测和执行逻辑。
Spring中的Resource体系,只是对资源类型进行了抽象,统一了接口,但是如果需要使用不同类型的Resource的时候,还是得通过new具体资源类型的方式来获取。Spring中为了简化对Resource的查找和加载,提供了ResourceLoader来专门负责加载Resource,使用这不需要关心如何加载具体的资源,只需要给出资源的定义(schame),剩下的就交由ResourceLoader来处理了。
ResourceLoader接口:
public interface ResourceLoader {
// ClassPathResource对应的Url的协议头(前缀)
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
// 根据给出的资源Url获取对应的Resource对象
Resource getResource(String location);
// 获取当前ResourceLoader所使用的ClassLoader
ClassLoader getClassLoader();
}
从接口定义中看ResourceLoader最主要的行为就是getResource操作。getResource方法接收一个字符串类型参数,通过对字符串参数的解析和处理返回对应的Resource对象,这里的location参数可以包含一定的协议头或前缀来表明参数的来源信息。
默认的实现DefaultResourceLoader,它可以使用ClassLoader作为参数进行创建。其getResource方法实现如下:
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith(CLASSPATH_URL_PREFIX)) { // classpath:前缀
// ClassPathResource需要使用Class或ClassLoader,这里传入ClassLoader
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
} else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location); // 如果不符合URL规范,则当做普通路径(如:test/resource.xml)处理
}
}
}
对于传入的location参数,显示判断了是否包含classpath:前缀,如果包含则返回ClassPathResource对象,如果不是则通过解析URL的方式解析location参数,如果参数符合URL规范,则创建一个UrlResource,如果资源既不包含classpath:特殊前缀,也不是URL形式,那么就将其当做普通的资源路径传递给getResourceByPath进行处理:
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
private static class ClassPathContextResource extends ClassPathResource implements ContextResource {
public ClassPathContextResource(String path, ClassLoader classLoader) {
super(path, classLoader);
}
public String getPathWithinContext() {
return getPath();
}
@Override
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
return new ClassPathContextResource(pathToUse, getClassLoader());
}
}
getResourceByPath返回ClassPathContextResource类型,实际上也可以也就是ClassPathResource。可以看出,ResourceLoader中并不判断资源是否真实存在和是否可读写,而仅仅通过解析出传入的location参数返回不同的Resource实现而已,资源是否存在,是否可读,需要调用方在拿到Resource对象后通过Resource提供的方法自行判断。
Spring除了提供ResourceLoader之外,还提供了ResourcePatternResolver来扩充ResourceLoader。引进了一个新的前缀:classpath*:。和classpath:的差别就是,classpath*:可以搜索class path下所有满足条件的资源(包括同名的资源),而classpath:则只能返回一个资源(即使存在多个)。
到此,关于“怎么理解Spring中的Resource与ResourceLoader体系”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/PrivateO2/blog/3079164