likes
comments
collection
share

揭开Spring资源加载的秘密(一)——资源的统一化表示

作者站长头像
站长
· 阅读数 2

思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。 作者:毅航😜


剖析Spring中对于资源的抽象设计,设计具有通用性的资源化统一表示方法。

前言

  • 加载配置文件ClassPathXmlApplicationContext的构造方法会接受一个或多个XML配置文件的类路径位置作为参数。这些配置文件包含了Spring应用程序的Bean定义和配置元数据。

  • 解析XML文件:一旦配置文件被加载,构造方法会解析XML文件,识别和解释XML中的各个元素,例如 <beans> <bean> 元素等。

  • 创建并初始化Spring容器:构造方法会创建一个Spring容器,用于管理和维护Bean信息。进一步,Spring容器会根据XML配置文件中定义的Bean信息创建相应的Bean实例,并根据配置文件中的属性和依赖关系进行初始化。

进一步,在我们的代码中,有关资源加载大致逻辑如下:

private void loadBeanDefinitions(BeanFactory beanFactory) {
    // 遍历路径地址信息
    for (String location : this.configLocations) {
        // 通过classLoader来加载配置文件信息
        try (InputStream inputStream 
                    = this.getClass().getClassLoader()
                .getResourceAsStream(location)) {
       // 省略xml解析过程  
}

乍一看,这样的设计似乎并没有什么不好。在使用时只需要将配置信息配置到resources下,然后提供相应的url信息,容器就能完成相关bean资源的加载,而且易于理解。

但写完之后你有考虑过其他的可能吗?例如,有些用户可能期待从文件中加载,另一些用户可能期待从网络上进行加载。不难发现,当面对这些需求时我们的程序就显得捉襟见肘了。这主要是在设计之初从来没有考虑到如此繁多的场景,因为我们假想的场景就是只从类路径下书写配置文件。

那么对用户层出不穷的需求,我们到底该如何来设计具有泛化性更强的资源加载呢?换句话说,在Spring内部,其又是如何来来对资源进行抽象化处理的呢?

不了解Spring内部设计也没关系,今天我们就来看一看Spring内部对资源加载模块是如何进行设计的。

资源的统一化抽象

在程序开发中,对于资源的定义其实非常宽泛。包括但不局限于网络资源,还包括二进制数据、文件、字节流等各种形式。而这些资源可以存放在各种不同的地方。例如,网络、文件系统、应用程序内部等。

为了解决实现资源的加载,JDK内部也提供了多种资源加载方式。例如, 我们可以借助 ClassLoader 加载类路径下的资源,或借助 File 来加载文件系统中的资源。

如上这些只是我们对于加载的手段,换句话说,我们已经知道了资源存放的格式,只需根据对应的格式调用对应的API进行操纵即可。但不知是否有过这样的考虑,如果我们事先并不知道文件格式,而需要我们自己来面对复杂多变的资源加载方式,这种情况下我们又该如何处理呢?

在考虑这个问题之前,我们不妨来考虑这样一个问题。即使资源存放形式以及存放位置各不相同,但最终交由计算机处理的话,其都会转为二进制比特流的形式进行加载,这是因为在计算机的世界中只有01两种状态。

顺着这个思路来继续思考,那在设计资源加载时,是不是可以将所有的资源抽象为文件流的形式?即不论你以什么形式传递,最终你都需要被解析为文件流的形式交由程序来处理。这样一分析,资源加载的设计是不是感觉也不是那么困难了?这就是面向对象设计中最关键的一点——抽象

明白了资源该抽象为什么形式后,我们接下来看看Spring内部是如何来做的。在Spring中,资源加载的顶层接口为InputStreamSource

进一步,InputStreamSourceSpring 框架中的一个接口,其主要用于提供资源的输入流。而资源的形式可以是各种类型的文件、URL、字节数组等。其内部也之定义了如下的一个方法:

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;

}

getInputStream()这个方法用于获取资源的输入流,允许应用程序访问资源的内容。通常,实现 InputStreamSource 接口的类会提供不同类型的资源加载方式,以便将资源的内容以输入流的形式传递给应用程序。

Resource——资源上层抽象

当资源抽象为一个后,下一步要做就是对资源的统一表示。正如之前所说,资源的存放可以是多样的,可以存放于文件、网络等位置,面对这么多复杂的存放逻辑,程序肯定不能通过大量if-else来判断资源存放形式,然后再根据文件类型来定义处理逻辑。

为了解决资源统一表示的问题,在Spring中定义了Resource接口来对待处理资源进行抽象表示。此时,资源可以是各种类型的数据。包括配置文件、图片、文本文件、模板文件等。换言之,Resource接口的主要目的是提供一种通用的方式来加载和访问这些资源,无论这些资源位于类路径、文件系统、URL或其他位置。

当有了用于对资源的统一表示后,不妨再来思考这样一个问题,这些资源会有哪些共性行为呢?大概你会想到如下几个共性行为,资源是否存在、资源是否可读,资源位置信息等。而这些行为统统也都被定义在Resource接口中,并被定义为如下的方法:

  1. boolean exists():用于检查资源是否存在。
  2. boolean isOpen():用于检查资源是否是可打开的,例如,是否是一个文件或可读取的流。
  3. URL getURL():获取资源的URL,适用于资源位于URL路径的情况。
  4. File getFile():获取资源的文件句柄,适用于资源位于文件系统的情况。
  5. String getFilename():获取资源的文件名,用于标识资源的名称。
  6. InputStream getInputStream():获取资源的输入流,用于读取资源的内容。

揭开Spring资源加载的秘密(一)——资源的统一化表示

如上这张图展示了 Resource相关实现类信息其中 FileSystemResource主要用于从文件系统加载资源。而UrlResource:用于从URL加载资源。同时,对于各类不同的资源Spring内部还支持通过不同的前缀来进行区分,具体来说,如果是 classpath 开头的资源路径,Spring 解析到后会自动去类路径下找;如果是 file 开头的资源路径,则会去文件系统中找;而如果是 URL 支持的协议开头,则底层会使用对应的协议,去尝试获取相应的资源文件。

总结

至此,我们也就对Spring内有关资源加载的统一化表示进行了分析,其实Spring内部对于资源的抽象逻辑也很简单,无非就是将各类资源加载的资源的共性特点,即在加载时都需要转换为二进制比特流这一特点,进而将抽象出顶层父接口InputStreamSource这一接口。 在此基础上为了更加照顾其他方式的资源,进而形成Resource接口,来方便文件加载的扩展。

希望文章对你了解Spring的资源加载以及面向对象中的抽象有所帮助。如果觉得文章不错,不妨点赞+收藏。我是毅航,我们下次再见!