1. Spring 没有使用 Java 原生的资源管理

资源管理其实我们还是熟悉的,例如 ClassLoader#getResource 或 ClassLoader#getResourceAsStream, 这些都是 JDK 原生的加载资源的方法,而我们前面学习 ApplicationContext 的时候,其中的 ClassPathXmlApplicationContext 的父类 AbstractXmlApplicationContext 中有一段如下的代码

  1. protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
  2. Resource[] configResources = getConfigResources();
  3. if (configResources != null) {
  4. reader.loadBeanDefinitions(configResources);
  5. }
  6. // 省略 ......
  7. }

这个方法通过组装 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 资源模型

image.png
通过 IDEA 生成简单的 Spring 资源模型结构,可以看到其实最顶级的不是 Resource 而是 InputStreamSource。

2.1 InputStreamSource

  1. public interface InputStreamSource {
  2. InputStream getInputStream() throws IOException;
  3. }

这个接口只有一个 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 ,说明它不会直接加载资源。

  1. public class EncodedResource implements InputStreamSource {
  2. private final Resource resource;
  3. // 省略 ......

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 加载文件系统中的资源
  • 通过 URL 和不同的协议加载本地 / 网络上的资源

    3.2 Spring 实现

  • ClassLoader -> ClassPathResource [ classpath:/ ]:解析到后会自动去类路径下找

  • File -> FileSystemResource [ file:/ ]:去文件系统中找
  • URL →->UrlResource [ xxx:/ ]:底层会使用对应的协议,去尝试获取相应的资源文件

除此之外还有 ServletContextResource 实现了 ContextResource,即去 ServletContext 域中寻找。