1. Spring 没有使用 Java 原生的资源管理
资源管理其实我们还是熟悉的,例如 ClassLoader#getResource 或 ClassLoader#getResourceAsStream, 这些都是 JDK 原生的加载资源的方法,而我们前面学习 ApplicationContext 的时候,其中的 ClassPathXmlApplicationContext 的父类 AbstractXmlApplicationContext 中有一段如下的代码
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
// 省略 ......
}
这个方法通过组装 XmlBeanDefinitionReader 来解析 xml 而它 load 的参数则是一个 Resource[]
这也就是 Spring 自己实现的资源模型。
为什么这样做呢?官网的说明:官网地址跳转
Java’s standard java.net.URL class and standard handlers for various URL prefixes, unfortunately, are not quite adequate enough for all access to low-level resources. For example, there is no standardized URL implementation that may be used to access a resource that needs to be obtained from the classpath or relative to a ServletContext. While it is possible to register new handlers for specialized URL prefixes (similar to existing handlers for prefixes such as http:), this is generally quite complicated, and the URL interface still lacks some desirable functionality, such as a method to check for the existence of the resource being pointed to. 机翻: 不幸的是,Java的标准java.net.URL类和各种URL前缀的标准处理程序对于所有底层资源的访问都不够充分。例如,没有标准化的URL实现可以用来访问需要从类路径或相对于ServletContext获取的资源。虽然可以为专业注册新处理程序URL前缀(类似于现有的前缀,如http处理程序:),这通常是非常复杂的,和URL接口仍然缺乏一些可取的功能,比如一个方法来检查存在的资源被指出。
简单的说,Java原生的 Resource 读取时没有采用一致的URI写法。
例如 http://
这种 URL的前缀是有的,但是想读取类路径时就不像 classpath://
这么友好,说白了就是为了规范,强迫症,而且实现也比较复杂混乱吧。
2. Spring 资源模型
通过 IDEA 生成简单的 Spring 资源模型结构,可以看到其实最顶级的不是 Resource 而是 InputStreamSource。
2.1 InputStreamSource
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
这个接口只有一个 getInputStream
方法,其实就是表明了,如果实现了 InputStreamSource
接口的实现类,就都需要具有取到资源的输入流的能力哈。
2.2 Resource
Interface for a resource descriptor that abstracts from the actual type of underlying resource, such as a file or class path resource.
这是一个资源描述符接口,它可以从基础资源的实际类型中抽象出来,例如文件或类路径资源。
我们的配置文件其实通常放在 Resouce 下,所以这种方式是更加合适的。
2.3 EncodedResource
EncodedResource
就是编码后的资源,源码中,可以看到它内部组合了 Resource
,说明它不会直接加载资源。
public class EncodedResource implements InputStreamSource {
private final Resource resource;
// 省略 ......
2.4 WritableResource
SpringFramework 3.1 之后,Resource
有了一个新的子接口:WritableResource
,它代表着“可写的资源”。 而我们的 Resource 就是可读呗。
2.5 ContextResource
跟 WritableResource
并列的还有一个 ContextResource
Extended interface for a resource that is loaded from an enclosing ‘context’, e.g. from a javax.servlet.ServletContext but also from plain classpath paths or relative file system paths (specified without an explicit prefix, hence applying relative to the local ResourceLoader’s context).
从一个封闭的“上下文”中加载资源的扩展接口,例如从javax.servlet.ServletContext中加载,但也从普通的类路径路径或相对文件系统路径(没有指定显式前缀,因此应用相对于本地ResourceLoader的上下文)。
它强调的是从一个封闭的 “上下文” 中加载,就类似 ServletContext
这种域。
3. 实现
3.1 Java 原生实现
- 通过 ClassLoader 加载类路径下的资源
- 通过 File 加载文件系统中的资源
-
3.2 Spring 实现
ClassLoader -> ClassPathResource [ classpath:/ ]:解析到后会自动去类路径下找
- File -> FileSystemResource [ file:/ ]:去文件系统中找
- URL →->UrlResource [ xxx:/ ]:底层会使用对应的协议,去尝试获取相应的资源文件
除此之外还有 ServletContextResource 实现了 ContextResource,即去 ServletContext 域中寻找。