思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
剖析Spring
中对于资源的抽象设计,设计具有通用性的资源化统一表示方法。
前言
在对Spring理解吗?手写一个Spring的Bean容器怎么样一文中,我们通过手动Coding
的方式实现了一个很简陋的ClassPathXmlApplicationContext
。其本质逻辑大致概括为如下几点:
-
加载配置文件 :
ClassPathXmlApplicationContext
的构造方法会接受一个或多个XML
配置文件的类路径位置作为参数。这些配置文件包含了Spring
应用程序的Bean
定义和配置元数据。 -
解析XML文件 :一旦配置文件被加载,构造方法会解析
XML
文件,识别和解释XML
中的各个元素,例如<beans> <bean>
元素等。 -
创建并初始化Spring容器 :构造方法会创建一个
Spring
容器,用于管理和维护Bean
信息。进一步,Spring
容器会根据XML
配置文件中定义的Bean
信息创建相应的Bean
实例,并根据配置文件中的属性和依赖关系进行初始化。
进一步,在我们的代码中,有关资源加载大致逻辑如下:
java
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
进行操纵即可。但不知是否有过这样的考虑,如果我们事先并不知道文件格式,而需要我们自己来面对复杂多变的资源加载方式,这种情况下我们又该如何处理呢?
在考虑这个问题之前,我们不妨来考虑这样一个问题。即使资源存放形式以及存放位置各不相同,但最终交由计算机
处理的话,其都会转为二进制比特流
的形式进行加载,这是因为在计算机的世界中只有0
和1
两种状态。
顺着这个思路来继续思考,那在设计资源加载时,是不是可以将所有的资源抽象为文件流
的形式?即不论你以什么形式传递,最终你都需要被解析为文件流
的形式交由程序来处理 。这样一分析,资源加载的设计是不是感觉也不是那么困难了?这就是面向对象设计
中最关键的一点------抽象
。
明白了资源
该抽象为什么形式后,我们接下来看看Spring
内部是如何来做的。在Spring
中,资源加载的顶层接口为InputStreamSource
。
进一步,InputStreamSource
是 Spring
框架中的一个接口,其主要用于提供资源的输入流。而资源的形式可以是各种类型的文件、URL、字节数组
等。其内部也之定义了如下的一个方法:
java
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
getInputStream()
这个方法用于获取资源的输入流,允许应用程序访问资源的内容。通常,实现 InputStreamSource
接口的类会提供不同类型的资源加载方式,以便将资源的内容以输入流的形式传递给应用程序。
Resource
------资源上层抽象
当资源抽象为一个流
后,下一步要做就是对资源的统一表示
。正如之前所说,资源
的存放可以是多样的,可以存放于文件、网络
等位置,面对这么多复杂的存放逻辑,程序肯定不能通过大量if-else
来判断资源存放
形式,然后再根据文件类型来定义处理逻辑。
为了解决资源统一表示的问题,在Spring
中定义了Resource
接口来对待处理资源进行抽象表示。此时,资源可以是各种类型的数据。包括配置文件、图片、文本文件、模板文件等 。换言之,Resource
接口的主要目的是提供一种通用的方式来加载和访问这些资源,无论这些资源位于类路径、文件系统、URL
或其他位置。
当有了用于对资源的统一表示
后,不妨再来思考这样一个问题,这些资源
会有哪些共性行为
呢?大概你会想到如下几个共性行为,资源是否存在、资源是否可读,资源位置信息
等。而这些行为统统也都被定义在Resource
接口中,并被定义为如下的方法:
boolean exists()
:用于检查资源是否存在。boolean isOpen()
:用于检查资源是否是可打开的,例如,是否是一个文件或可读取的流。URL getURL()
:获取资源的URL
,适用于资源位于URL
路径的情况。File getFile()
:获取资源的文件句柄,适用于资源位于文件系统的情况。String getFilename()
:获取资源的文件名,用于标识资源的名称。InputStream getInputStream()
:获取资源的输入流,用于读取资源的内容。
如上这张图展示了 Resource
相关实现类信息其中 FileSystemResource
主要用于从文件系统加载资源。而UrlResource
:用于从URL加载资源。同时,对于各类不同的资源Spring
内部还支持通过不同的前缀来进行区分,具体来说,如果是 classpath
开头的资源路径,Spring
解析到后会自动去类路径下找;如果是 file
开头的资源路径,则会去文件系统中找;而如果是 URL
支持的协议开头,则底层会使用对应的协议,去尝试获取相应的资源文件。
总结
至此,我们也就对Spring
内有关资源加载的统一化表示进行了分析,其实Spring
内部对于资源的抽象逻辑也很简单,无非就是将各类资源加载的资源
的共性特点,即在加载时都需要转换为二进制比特
流这一特点,进而将抽象出顶层父接口InputStreamSource
这一接口。 在此基础上为了更加照顾
其他方式的资源,进而形成Resource
接口,来方便文件加载的扩展。
希望文章对你了解Spring
的资源加载以及面向对象中的抽象
有所帮助。如果觉得文章不错,不妨点赞+收藏。我是毅航,我们下次再见!