SpringBoot源码解读与原理分析(四)SPI机制

文章目录

    • 2.4 SPI机制(Service Provider Interface)
      • 2.4.1 JDK原生SPI
        • 1.定义接口+实现类
        • 2.声明SPI文件
        • 3.测试
      • 2.4.2 SpringFramework 3.2 的SPI
        • 1.声明SPI文件
        • 2.测试
        • 3.Spring SPI机制的实现原理

2.4 SPI机制(Service Provider Interface)

  • 问题:依赖倒转原则提到,应该依赖接口而不是实现类,但接口最终要有实现类落地。如果因为业务调整需要替换某个接口的实现类,就不得不改动实现类,也就是修改源码。
  • 解决:SPI机制解决了这个问题。通过一种“服务寻找”的机制,动态地加载接口/抽象类对应的具体实现类。把接口的具体实现类的定义和声明交给了外部化的配置文件。
  • 如下图,一个接口可以有多个实现类,通过SPI机制,可以将一个接口需要创建的实现类的对象都罗列到一个特殊的文件中,SPI机制会依次将这些实现类的对象进行创建并返回。
    SPI设计示意图

2.4.1 JDK原生SPI

简单了解即可,使用范围有限,只能通过接口或抽象类来加载具体的实现类。

1.定义接口+实现类

模拟一套Dao接口的不同数据库访问支持

public interface DemoDao {
}
public class DemoMysqlDao implements DemoDao {
}
public class DemoOracleDao implements DemoDao {
}
2.声明SPI文件

JDK的SPI需要遵循以下规范:

  • 所有定义的SPI文件都必须放在项目的META-INF/services目录下
  • 文件名必须命名为接口或抽象类的全限定名
  • 文件内容为接口或抽象类的具体实现类的全限定名;如果有多个,则每行声明一个具体实现类的全限定名,多个类之间没有分隔符

具体步骤:
(1)在resources目录下创建新目录META-INF/services
(2)新建文件:com.star.springboot.spi.DemoDao
(3)输入文件内容:

com.star.springboot.spi.DemoMysqlDao
com.star.springboot.spi.DemoOracleDao

声明SPI文件

3.测试
public class JdkSpiApplication {public static void main(String[] args) {ServiceLoader<DemoDao> serviceLoader = ServiceLoader.load(DemoDao.class);serviceLoader.iterator().forEachRemaining(dao -> {System.out.println(dao);});}}

输出结果:

com.star.springboot.spi.DemoMysqlDao@65b3120a
com.star.springboot.spi.DemoOracleDao@6f539caf

控制台成功打印出DemoDao的两个实现类对象,这说明JDK原生的SPI机制已成功使用。

2.4.2 SpringFramework 3.2 的SPI

SpringFramework中的SPI比JDK原生的SPI更高级实用,因为它不仅限于接口或抽象类,而可以是任何一个类、接口或注解
SpringBoot中大量用到SPI机制加载自动配置类和特殊组件等(如@EnableAutoConfiguration)。

1.声明SPI文件

SpringFramework的SPI需要遵循以下规范:

  • SPI文件必须放在项目的META-INF目录下。
  • 文件名必须命名为spring.factories(实际上是一个properties文件)。
  • 文件内容:被检索的类/接口/注解的全限定名作为properties的key,具体要检索的类的全限定名作为value,多个类之间用英文逗号隔开。

具体步骤:
(1)在resources/META-INF目录下新建文件:spring.factories
(2)输入文件内容:

com.star.springboot.spi.DemoDao=com.star.springboot.spi.DemoMysqlDaoImpl,com.star.springboot.spi.DemoOracleDaoImpl

声明SpringFramework的SPI文件

2.测试
public class SpringSpiApplication {public static void main(String[] args) {List<DemoDao> demoDaos = SpringFactoriesLoader.loadFactories(DemoDao.class, SpringSpiApplication.class.getClassLoader());demoDaos.forEach(dao -> {System.out.println(dao);});System.out.println("----------");List<String> daoClassNames = SpringFactoriesLoader.loadFactoryNames(DemoDao.class, SpringSpiApplication.class.getClassLoader());daoClassNames.forEach(className -> {System.out.println(className);});}
}

输出结果:

com.star.springboot.spi.DemoMysqlDaoImpl@52d455b8
com.star.springboot.spi.DemoOracleDaoImpl@4f4a7090
----------
com.star.springboot.spi.DemoMysqlDaoImpl
com.star.springboot.spi.DemoOracleDaoImpl

控制台成功打印出DemoDao的两个实现类对象及其全限定名,这说明SpringFramework的SPI机制已成功使用。

延伸:
SpringFactoriesLoader不仅可以加载声明的类的对象(loadFactories),还可以直接把预定义好的全限定名提取出来(loadFactoryNames)。

3.Spring SPI机制的实现原理

SPI的核心使用方法是SpringFactoriesLoader.loadFactoryNames,通过这个方法可以获得指定全限定名对应配置的所有类的全限定名。

// 规定SPI文件名称及位置
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// 存储SPI机制加载的类及其映射
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {String factoryTypeName = factoryType.getName();// 利用缓存机制提高加载速度return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {// 解析之前先检查缓存,有则直接返回MultiValueMap<String, String> result = cache.get(classLoader);if (result != null) {return result;}try {// 真正的加载动作,利用类加载器加载所有的spring.factories(多个,包括我们自定义框架本身自带的),并逐个配置解析Enumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));result = new LinkedMultiValueMap<>();while (urls.hasMoreElements()) {// 提取出每个spring.factories文件URL url = urls.nextElement();UrlResource resource = new UrlResource(url);// 以properties的方式读取Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {// 逐个收集key和valueString factoryTypeName = ((String) entry.getKey()).trim();// 如果一个key配置了多个value,使用英文逗号分割for (StrinfactoryImplementationName:StringUtils.commaDelimitedListToStringArray((Strinentry.getValue())) {result.add(factoryTypeName, factoryImplementationName.trim());}}}// 存入缓存中cache.put(classLoader, result);return result;} catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location ["+FACTORIES_RESOURCE_LOCATION + "]", ex);}
}

逻辑梳理:SpringFactoriesLoader中有一块缓存区,这块缓存区会在SPI机制第一次被利用时,将项目类路径下所有的spring.factories文件都加载并解析,然后存入缓存区。解析的具体逻辑,是将每一个spring.factories文件都当作properties文件解析,提取每一对映射关系,保存到Map中,最终存入全局缓存。
通过Debug,可以看到SPI机制不仅读取自定义的spring.factories,还读取了框架自带的:
读取框架自带的spring.factories
最终保存到Map的映射关系非常多,但返回给main只有自己定义的:
保存到Map的映射关系
······

本节完,更多内容请查阅分类专栏:SpringBoot源码解读与原理分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/721742.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【c++设计模式14】结构型6:享元模式(Flyweight Pattern)

【c设计模式14】结构型6&#xff1a;享元模式&#xff08;Flyweight Pattern&#xff09; 一、定义二、适用场景三、过程四、享元模式类图五、C示例代码六、使用注意事项 类型序号设计模式描述结构型1适配器模式&#xff08;Adapter Pattern&#xff09;它用于在不修改已有类的…

配置ssh连接Esxi、ESXi 收缩虚拟硬盘文件(.vmdk) 大小(回收ESXi thin磁盘空间)

文章目录 一、配置ssh连接Esxi1. Esxi开启ssh服务2. 如何设置ESXI主机启动时自动开启shell(ssh)服务 二、ESXi6.0中将虚拟机从厚置备转换为精简置备三、ESXi 收缩虚拟硬盘文件(.vmdk) 大小1. 三种虚拟磁盘类型2. 如何给ESXi 上的VM缩小硬盘&#xff08;VMDK&#xff09;回收ESX…

【C++精简版回顾】13.(重载1)运算符重载+,前置后置++

1.友元函数方式为类重载运算符 &#xff08;友元函数声明可以放在类任何地方&#xff09; 1.类 class MM { public:MM() {}MM(int grade,string name):grade(grade),name(name){}friend MM operator(MM object1, MM object2);void print() {cout << this->grade <…

单调队列的使用

单调队列其实就是一个队列&#xff0c;只是使用了一点巧妙的方法使得队列中的元素全都是单调递增&#xff08;或单调递减&#xff09;的 单挑队列主要解决以下问题&#xff1a; 滑动窗口在滑动时&#xff0c;r代表右侧数字进入串口&#xff0c;l代表左侧数字出窗口 这个过程…

Spring事件发布监听器ApplicationListener原理- 观察者模式

据说监听器模式也是mq实现的原理, 不过mq我还没来得及深入学习, 先用spring来理解一下吧 Spring事件发布监听器ApplicationListener原理- 观察者模式 什么是观察者模式一个Demo深入认识一下观察者模式Spring中的事件发布监听ps 什么是观察者模式 大家都听过一个故事叫做烽火戏…

数据结构与算法-希尔排序

引言 在计算机科学中&#xff0c;数据结构和算法是构建高效软件系统的基石。而排序算法作为算法领域的重要组成部分&#xff0c;一直在各种应用场景中发挥着关键作用。今天我们将聚焦于一种基于插入排序的改进版本——希尔排序&#xff08;Shell Sort&#xff09;&#xff0c;深…

证明高维度神经网络模型是低纬度神经网络模型的加和

神经网络中矩阵乘法的分解与应用 启发标题&#xff1a;神经网络中矩阵乘法的分解与应用摘要&#xff1a;引言&#xff1a;方法&#xff1a;实验&#xff1a;结论&#xff1a;参考文献&#xff1a;附录1附录2实验数据 启发 理论上 更具矩阵乘法 A[p,mn]B[mn,q]C[p,q] Acat(A[:,…

AAC ADTS格式

AAC⾳频格式&#xff1a;Advanced Audio Coding(⾼级⾳频解码)&#xff0c;是⼀种由MPEG-4 标准定义的有损⾳频压缩格式&#xff0c;由Fraunhofer发展&#xff0c;Dolby, Sony和AT&T是主要的贡献者。 ADIF&#xff1a;Audio Data Interchange Format ⾳频数据交换格式。这…

一次奇特的应急响应

访问polling.oastify.com 今天&#xff08;2024/3/5&#xff09;在深信服防火墙用户安全日志页面&#xff0c;检测到我的主机在和polling.oastify.com域名进行通信 当时通知我检查我的主机&#xff0c;慌得一批&#xff0c;检查完后可能认为是我代理的问题&#xff0c;把代理关…

w2v参数报错_TypeError: init() got an unexpected keyword argument ‘size‘

1.错误方式 w2v Word2Vec(docs,size16, sg1, window5, seed2020, workers24, min_count1, iter1) 在linux操作环境下&#xff0c;报错显示&#xff1a; TypeError: init() got an unexpected keyword argument ‘size’ 在vscode软件上&#xff0c;查看当前w2v参数 2.正确…

unocss 究竟比 tailwindcss 快多少?

unocss 究竟比 tailwindcss 快多少&#xff1f; 前言 我们知道 unocss 很快&#xff0c;也许是目前最快的原子化 CSS 引擎 (没有之一)。 unocss 解释它为什么这么快的原因&#xff0c;是因为它不用去解析 CSS 抽象语法树&#xff0c;直接在 content 里面通过正则表达式从内容…

yum 和 rpm

rpm说明 rpm -qa &#xff1a;列出所有已安装的软件包 [roothub ~] rpm -qa geoipupdate-2.5.0-1.el7.x86_64 ncurses-base-5.9-14.20130511.el7_4.noarch libndp-1.2-9.el7.x86_64 libfastjson-0.99.4-3.el7.x86_64 。。。 rpm -qf FILENAME &#xff1a;查找提供 FILENAME…

Nginx使用—http基础知识

web访问流程 当我们在客户端通过浏览器输入网址的时候&#xff0c;这时候是访问不到服务器的&#xff0c; 先会去找到DNS解析服务器&#xff0c;DNS解析服务器返回IP地址&#xff0c; 客户端通过http协议向服务端发送请求&#xff0c;服务器响应请求并返回对应的资源给客户端&a…

H5小游戏,斗地主

H5小游戏源码、JS开发网页小游戏开源源码大合集。无需运行环境,解压后浏览器直接打开。有需要的,私信本人,发演示地址,可以后再订阅,发源码,含60+小游戏源码。如五子棋、象棋、植物大战僵尸、开心消消乐、扑鱼达人、飞机大战等等 <!DOCTYPE html> <html> <…

鸿蒙Harmony应用开发—ArkTS声明式开发(通用属性:组件内容模糊)

为当前组件添加内容模糊效果。 说明&#xff1a; 从API Version 10开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 foregroundBlurStyle foregroundBlurStyle(value: BlurStyle, options?: ForegroundBlurStyleOptions) 为当前组件提供…

北京Excel表格线下培训班

Excel培训目标 熟练掌握职场中Excel所需的公式函数计算&#xff0c;数据处理分析&#xff0c;各种商务图表制作、动态仪表盘的制作、熟练使用Excel进行数据分析&#xff0c;处理&#xff0c;从复杂的数据表中把数据进行提取汇总 Excel培训形式 线下面授5人以内小班&#xff…

最新AI系统ChatGPT网站H5系统源码,支持Midjourney绘画

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

外包干了6个月,技术退步明显

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入广州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

UE4c++ 材质功能大全(想起来就补充一个)

前言&#xff1a;才想起写一个这个文档&#xff0c;前期内容较少&#xff0c;其他内容&#xff0c;我也只会想起来加一加&#xff01; 材质功能大全 竖直百分比进度HSV To RGBRGB转灰度值AlphaComosote(Premultiplied Alpha&#xff09;预乘 转 Translucent &#xff08;sRGB与…

Hello World!第一个labview程序

软件版本&#xff1a; labview myrio 2021英文版 因为没有找到中文版的&#xff0c;据说是myrio没有中文版本 实验内容&#xff1a; 文本显示&#xff0c;程序界面输入任意文本&#xff0c;然后运行程序 在前面板显示出输入的文本 以下为具体步骤&#xff1a; 第一步&…