思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
在揭开Spring资源加载的秘密(一)------资源的统一化表示中,我们详细的对Spring
中的资源的统一表示进行了分析。总的来说Spring
内部在对资源进行处理时,会将资源抽象为二进制
流的形式,并在此基础上在对资源进行抽象为Resource
接口。而本文我们将对Spring
中的资源加载器ResourceLoader
进行分析。
前言
在介绍Spring
的资源加载之前,我们首先对Spring
中资源加载的关键类进行一个基本介绍。在Spring
内部,与资源加载相关的类主要有两个,一个是我们之前介绍过的Resoure
接口,其主要对资源统一化表示。另一个重要的接口就是ResourceLoader
,该接口内部如下所示:
java
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}
不难发现, ResourceLoader
接口包含两个关键方法:getResource
和 getClassLoader
。其中 getResource
方法用于获取指定位置的资源,并返回一个 Resource
对象。而getClassLoader()
方法用于获取与此资源加载器关联的类加载器。
对于类加载器你可会感到陌生,不过也不要慌。这里获取类加载器的主要作用在于获得与当前 ResourceLoader
相关联的类加载器,以便顺利进行资源加载的相关操作。
知晓了ResourceLoader
的相关作用后,接下来我们来对Spring
中的默认资源加载器DefaultResourceLoader
进行分析。
Spring
中的默认资源加载器
DefaultResourceLoader
作为Spring
中资源加载器的核心实现,它提供了一种从不同来源加载资源的机制。由于该类实现自ResourceLoader
,因此如果要深入了解Spring
中有关资源加载的相关逻辑,只需重点分析其内部的getResource
方法进行分析即可。这里之所以不分析getClassLoader()
方法,主要是因为其逻辑很简单,无非就是获取当前类的类加载罢了,逻辑非常简单,故不花费篇幅来进行分析。
接下来,我们来看DefaultResourceLoader
中getResource
方法实现的相关细节。其方法内部的逻辑大致如下:
java
public Resource getResource(String location) {
// <1> 判断资源协议信息,对资源协议进行解析
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
<2> 如果以/ 开头、则创建 ClassPathContextResource、其实也是一个 ClassPathResource 是他的子类、 类路径的资源
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
// <3> 如果以 classpath 开头 则 创建ClassPathResource
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// 尝试从网络与文件中获取
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
// 省略异常相关逻辑
}
}
(注:这里有关类路径信息classpath:
的描述在揭开Spring资源加载的秘密(一)------资源的统一化表示 中有详细描述,在此我们便不赘述)
在getResource
方法内部,其首先会对类内部ProtocolResolver
进行遍历,并调用其resolve
方法,如果可以返回一个Resource
就不再继续执行。对于这里提到的ProtocolResolver
我们有必要进行一下解释。
在Spring
内部ProtocolResolver
主要用于解析特定协议的资源。其主要作用是在资源加载过程中,根据指定的协议提供自定义的资源解析策略。 明白了这一点后,再回看<1>
处代码的逻辑是不是就很清晰了?
其实<1>
处代码的逻辑无非就是调用相关的资源解析器,并对资源进行解析,如果可以将传入的路径进行解析,就返回一个Resurce
并结束遍历。
如果你再深入分析DefaultResourceLoader
内部实现,你会发现DefaultResourceLoader
内部似乎没有初始化protocolResolvers
的地方。换句话,在DefaultResourceLoader
创建时,并不会初始化protocolResolvers
的内容。即如果我们如果不手动调用addProtocolResolver
方法向protocolResolvers
添加内容,那么protocolResolvers
便不会有内容。
讲到这里,不知道你是否切实感受到了Spring
内部在扩展性方面所作的设计。正如我们之前说的,对于资源我们常见的路径信息有classPath,url,file:
等几种不同的类型,但我们谁都不确定未来的某一天是否会有一个新的资源加载地址。
如果我们做设计,对于这些新增的资源加载方式,我们大概率通过if-else
来进行判断,然后每种决策对应不同的处理逻辑就能做到所谓的适配
。这样虽然能解决问题,但是其实是违反代码开闭原则
的,而Spring
作为一款框架,其在设计上肯定要考虑开闭原则
,尽量将修改范围缩小。
因此,其当发现某种情况无法进行资源加载后,其肯定不能通过在框架中加入if-else
来进行判断。为此Spring
内部将处理权交给我们,如果某一天我们真的希望从一些特殊的地方加载资源,我们只需集成protocolResolvers
接口,并通过addProtocolResolver
方法将其加入到DefaultResourceLoader
中的protocolResolvers
即可。或许或许
这样的设计巧妙吗?十分巧妙!更进一步,你能想到这是那种设计模式的应用吗?
这在某种程度上可以认为是责任链模式的扩展实现。而有关责任链相关的分析可参考笔者之前责任链模式在SpringMVC中的应用进行了解。
具体到这个例子来看,资源加载器DefaultResourceLoader
内部可能持有多个protocolResolver
,用于对不同的协议进行解析,每一种资源本身只能被一种 ProtocolResolver
进行解析,而这里如果资源无法被 ProtocolResolver
解析,则会继续交给下一个 ProtocolResolver
进行解析。其最终的结果无非就是,解析为Resource
或是没有一个 ProtocolResolver
能处理这个请求并返回一个 Resource
,这点其实很类似于SpringMVC
中拦截器的设计。
说到这里不知道你是否有这样的感觉,即感觉到源码之间很多设计其实是相同的,只要你掌握一处的设计思想,很多类似的设计都能触类旁通!这其实就是我们读源码,研究框架的意义所在。我们并不是为了去造轮子,而是期待在阅读源码中潜移默化的提升我们的编码、设计的能力。
往后<2>、<3>
处代码的逻辑其实也就非常简单了,大致逻辑无非就是判断资源前缀信息,如果资源路径以 classpath:
开头,它会创建一个 ClassPathResource
对象;如果以 file:
开头,则创建一个 FileSystemResource
对象。换句话,在默认情况下DefaultResourceLoader
初始化时就具备了对 classpath:
和 URL
协议的资源加载支持。
总结
至此,我们也就对Spring
内部的资源加载器有了一个宏观的认知,接下来我们来对本文内容进行一个简短的回顾和总结。首先,我们以Spring
中资源处理为切入点,分析了Spring
中的资源加载器的相关设计,并重点对其内部的DefaultResourceLoader
进行了详细的分析。
总的来看,DefaultResourceLoader
的工作原理大致如下:
- 当请求加载资源时,
DefaultResourceLoader
会根据资源的路径或标识符来确定资源类型,并创建相应类型的Resource
实例。 - 而这些
Resource
对象本身便提供了统一的方法来访问资源,即getInputStream()
方法,这样无论的做法上可以接在存放在不同位置的资源。无论实际资源是位于文件系统、类路径还是网络上都可以进行加载。
至此,我们也就花费了两章的内容了来对Spring
内部的资源加载进行了分析。Spring
的源码浩如烟海,所以不要期望能通过一两篇文章便能掌握Spring
框架的源码,笔者
的文章一定程度上只能算作是帮你入门Spring
源码的辅助工具罢了,至于能走多远还是要靠你自己的努力!
最后,希望文章对你有所帮助!