Spring用Resource接口抽象所有的底层资源,包括File、ClassPath、URL等。ResourceLoader接口是Resource的加载器,根据资源的路径/路径模式获取Resource实例。

Resource

接口定义

Resource接口的定义如下:

public interface Resource extends InputStreamSource {boolean exists();default boolean isReadable() {return exists();}default boolean isOpen() {return false;}default boolean isFile() {return false;}URL getURL() throws IOException;URI getURI() throws IOException;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;@NullableString getFilename();String getDescription();}

它继承自InputStreamSource接口,这个接口只有一个方法:

public interface InputStreamSource {InputStream getInputStream() throws IOException;}

这里比较重要的几个方法有:

  • getInputStream(),定位并且打开当前资源,返回该资源的一个InputStream。每次调用都会返回一个新的InputStream。
  • exists(),返回该资源是否物理存在。
  • getURL(),返回该资源的URL句柄。
  • getURI(),返回该资源的URI句柄。
  • isFile(),是否是文件。
  • getFile(),返回该资源的File句柄。

Resource接口的实现

Spring提供了多种Resource实现,我们可以直接使用。

FileSystemResource

FileSystemResource是针对java.io.File的Resource实现类,其构造函数与getInputStream()方法如下:

public class FileSystemResource extends AbstractResource implements WritableResource {private final String path;@Nullableprivate final File file;private final Path filePath;public FileSystemResource(String path) {Assert.notNull(path, "Path must not be null");this.path = StringUtils.cleanPath(path);this.file = new File(path);this.filePath = this.file.toPath();}//other constructors......@Overridepublic InputStream getInputStream() throws IOException {try {return Files.newInputStream(this.filePath);}catch (NoSuchFileException ex) {throw new FileNotFoundException(ex.getMessage());}}...
}

可以看到它封装了java.io.File,在获取输入流时就是打开了该文件的NIO文件流。

ClassPathResource

ClassPathResource是类路径下资源的Resource实现。它通过ClassLoader或Class来加载资源。其构造函数和getInputStream()方法的实现如下:

public class ClassPathResource extends AbstractFileResolvingResource {private final String path;@Nullableprivate ClassLoader classLoader;@Nullableprivate Class<?> clazz;public ClassPathResource(String path, @Nullable 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;this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());}//other constructors...public InputStream getInputStream() throws IOException {InputStream is;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;}...}

可以看到它最终委托Class对象或ClassLoader对象来加载资源。如果创建该ClassPathResource时指定了用于加载资源的Class对象,它会通过该Class对象的getResourceAsStream来加载资源;否则,如果指定了ClassLoader,它会通过该ClassLoader对象的getResourceAsStream来加载资源;否则,通过系统ClassLoader来加载资源。

UrlResource

UrlResource是java.net.URL的Resource实现类,用来访问URL可以正常访问的任意对象。其内部封装了URL对象,因此,它支持http、https、file、ftp、jar等协议。其getInputStream()方法实现如下:

public InputStream getInputStream() throws IOException {URLConnection con = this.url.openConnection();ResourceUtils.useCachesIfNecessary(con);try {return con.getInputStream();}catch (IOException ex) {// Close the HTTP connection (if applicable).if (con instanceof HttpURLConnection) {((HttpURLConnection) con).disconnect();}throw ex;}
}

可以看到它是打开一个新的到该URL所引用的远程对象的连接,获取URLConnection对象,然后获取输入流。

InputStreamResource

InputStreamResource把一个InputStream封装为Resource,其内部实现大致如下:

public class InputStreamResource extends AbstractResource {private final InputStream inputStream;private final String description;private boolean read = false;public InputStreamResource(InputStream inputStream, @Nullable String description) {Assert.notNull(inputStream, "InputStream must not be null");this.inputStream = inputStream;this.description = (description != null ? description : "");}...@Overridepublic InputStream getInputStream() throws IOException, IllegalStateException {if (this.read) {throw new IllegalStateException("InputStream has already been read - " +"do not use InputStreamResource if a stream needs to be read multiple times");}this.read = true;return this.inputStream;}...
}

ByteArrayResource

ByteArrayResource把字节数组封装为Resource。其实现大致如下:

public class ByteArrayResource extends AbstractResource {private final byte[] byteArray;private final String description;public ByteArrayResource(byte[] byteArray, @Nullable String description) {Assert.notNull(byteArray, "Byte array must not be null");this.byteArray = byteArray;this.description = (description != null ? description : "");}...@Overridepublic InputStream getInputStream() throws IOException {return new ByteArrayInputStream(this.byteArray);}...
}

ResourceLoader

接口定义

ResourceLoader的接口定义如下:

public interface ResourceLoader {Resource getResource(String location);@NullableClassLoader getClassLoader();
}

其中,getResource(String)接口返回指定位置的资源,getClassLoader()接口返回该ResourceLoader所使用的ClassLoader。

ResourceLoader接口的实现

ApplicationContext

Spring应用上下文都实现了ResourceLoader接口,因此所有的应用上下文都可以通过getResource(String)方法获取Resource实例。

DefaultResourceLoader

DefaultResourceLoader是默认的ResourceLoader实现,同时它也是AbstractApplicationContext类的基类。它的实现大致如下:

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(@Nullable ClassLoader classLoader) {this.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 <T> Map<Resource, T> getResourceCache(Class<T> valueType) {return (Map<Resource, T>) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>());}@Overridepublic Resource getResource(String location) {Assert.notNull(location, "Location must not be null");for (ProtocolResolver protocolResolver : this.protocolResolvers) {Resource resource = protocolResolver.resolve(location, this);if (resource != null) {return resource;}}if (location.startsWith("/")) {return getResourceByPath(location);}else if (location.startsWith(CLASSPATH_URL_PREFIX)) {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 (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));}catch (MalformedURLException ex) {// No URL -> resolve as resource path.return getResourceByPath(location);}}}...
}

可以看到,在创建DefaultResourceLoader的时候可以指定类加载器,如果没有指定,就会使用默认的类加载器。

同时,该类维护了一个ProtocolResolver集合,用来处理其他协议。ProtocolResolver是一个接口,其源码如下:

@FunctionalInterface
public interface ProtocolResolver {@NullableResource resolve(String location, ResourceLoader resourceLoader);}

这是一个函数接口,其唯一的抽象方法resolve,第一个参数是资源路径,第二个参数是针对该类协议的资源的资源加载器。

然后我们看一下DefaultResourceLoader的getResource(String)方法,该方法首先通过用户自己注册的ProtocolResolver尝试解析和加载资源,如果成功则直接返回;若不成功,则判断资源路径是否以/开头,如果以/开头,则直接返回一个ClassPathContextResource,若不是以/开头,但是以classpath:开头,则直接返回一个ClassPathResource,否则,根据协议返回FileUrlResource或UrlResource。

ResourcePatternResolver

ResourcePatternResolver是继承自ResourceLoader接口的一个接口,用来解析路径模式,例如Ant风格的路径模式,其源码如下:

public interface ResourcePatternResolver extends ResourceLoader {String CLASSPATH_ALL_URL_PREFIX = "classpath*:";Resource[] getResources(String locationPattern) throws IOException;
}

其中,getResources(String)接口根据输入的路径模式,返回Resource数组。

ResourcePatternResolver的一个实现是PathMatchingResourcePatternResolver,它可以解析指定的资源位置路径到一个或多个匹配的Resource,这里,资源路径可以是一个指向某一个特定的资源的简单路径,也可以是包含classpath*:前缀的路径,路径里还可以使用Ant风格正则表达式。例如,你可以使用以下这些路径:

  • file:C:/context.xml;
  • classpath:/context.xml;
  • /WEB-INF/context.xml;
  • /WEB-INF/*-context.xml;
  • file:C:/some/path/*-context.xml;
  • com/mycompany/**/applicationContext.xml
  • classpath:com/mycompany/**/applicationContext.xml
  • classpath*:META-INF/*-beans.xml

Ant风格正则表达式的细节可以参见这篇文章:https://jinnianshilongnian.iteye.com/blog/1416322。

Spring Resource和ResourceLoader源码解析相关推荐

  1. spring 注解试事物源码解析

    spring 注解试事物源码解析 基于xml注解式事务入口 public class TxNamespaceHandler extends NamespaceHandlerSupport {stati ...

  2. Spring事件机制Event源码解析(未完待续)

    Spring事件机制Event源码解析(未完待续) 监听器: ApplicationEvent事件 ApplicationListener监听器(观察者) ApplicationEventMultic ...

  3. Spring AOP 超详细源码解析

    知识章节 基础知识 什么是 AOP AOP 的全称是 "Aspect Oriented Programming",即面向切面编程 在 AOP 的思想里面,周边功能(比如性能统计,日 ...

  4. Spring之循环依赖源码解析

    目录 1.什么是循环依赖? 2.为什么会出现循环依赖? 3.面对循环依赖问题,我们该如何思考解决? 4.Spring是怎么解决循环依赖的? 5.总结 1.什么是循环依赖? 有两个类Order.Cust ...

  5. 【Spring Boot实战】源码解析Spring Boot自动配置原理

    一.简介 Spring致力于让Java开发更简单,SpringBoot致力于让使用Spring进行Java开发更简单,SpringCloud致力于基于SpringBoot构建微服务生态圈,让微服务开发 ...

  6. Spring之依赖注入源码解析

    依赖注入底层原理流程图:Spring中Bean的依赖注入原理 | ProcessOn免费在线作图,在线流程图,在线思维导图 | 1.Spring中到底有几种依赖注入方式 手动注入和自动注入 1.手动注 ...

  7. Spring aware使用与源码解析

    目录 1. aware接口的作用 2. 常用aware接口及作用 3. 使用样例:ApplicationContextAware 在Bean中获取上下文 4. 自定义aware的方式 4.1 定义继承 ...

  8. spring 启动之全过程 源码解析

    主类代码 public class BeanLifeCycle {public static void main(String[] args) {System.out.println("现在 ...

  9. 死磕Spring系列:SpringAOP源码解析

    1. 术语 在SpringAOP中,有很多专业术语,已经了解的小伙伴可以略过,不了解的朋友就需要注意看一下啦,不然会很懵的哟. Aspect(切面) 跨多个类的关注点的模块化,与垂直调用的业务树不同, ...

最新文章

  1. 写出一个超强的lighttpd模块
  2. VMware workstation安装
  3. LARS 最小角回归算法简介
  4. 页面加载完后立刻执行JS的两种方法
  5. php 去除 html 属性,用PHP 去掉所有html标签里的部分属性
  6. linux下mysql5.7修改密码
  7. XP无法建立宽带连接的解决方法
  8. rust睡觉按键没反应_腐蚀Rust有哪些实用操作 腐蚀Rust实用操作汇总-游侠网
  9. Java完全自学手册,从外包到大厂,再到年薪100万都靠它
  10. 服务器IP被封怎么办
  11. 电话机上面的接头RJ11
  12. WebStorm下载其他版本(历史版本)
  13. java backoff_Java BackOff类代码示例
  14. AD(Altium Designer)导出BOM时出错处理
  15. service两种启动方式的区别
  16. S3C2440系统中断(转)
  17. DVB-subtitle解析流程浅
  18. c语言char s[] 语句,35、若有定义和语句: char s[10]=abcd;printf(%s\n,s); 则结果是(以下u代表空格)...
  19. HTTPS原理解析-转
  20. Web应用架构搭建漏洞概念HTTP

热门文章

  1. 别停特斯拉旁边!特斯拉车辆自燃全车烧毁 连旁边的奥迪都没放过...
  2. 提高django model效率的几个小方法
  3. linux pxe安装视频,Linux—图解PXE实现全自动安装系统(1)
  4. python比java难吗-Python 的开发效率真的比 Java高吗?
  5. rtmp服务器 协议之同步
  6. 看门狗超时前在内核打印信息
  7. html 换行_李亚涛:清除HTML所有格式并且删除换行与回车,只显示文本
  8. python中合法的二进制整数_python:求整数的二进制表示
  9. 【Spring】Bean instantiation via constructor failed nested exception Constructor threw exception
  10. 95-18-015-配置-AbstractBootstrapConfig