HOME> 国足世界杯夺冠> 【一文读懂】SPI机制之JAVA的SPI实现详解

【一文读懂】SPI机制之JAVA的SPI实现详解

🐻大家好,我是木木熊

🌍️公众号:「程序员木木熊 」

本文以学习交流和分享为目的,如有不正确的地方,欢迎大家批评指正!!

文章较长,但内容超值,可以先收藏、关注~~

什么是SPI

SPI全称Service Provider Interface,翻译过来是“服务提供者的接口”,单从这个翻译,确实让人摸不着头脑。

我们来看看一些常见的关于SPI说法

SPI是一种在Java中用于实现模块化和可插拔性的机制。

SPI是JDK内置的一种服务发现机制,可以用来启用框架扩展和替换组件。

SPI是JDK内置的一种服务发现机制,用于制定一些规范,实际实现方式交给不同的服务厂商

SPI机制保证多个实现可以动态地加载和使用,在不修改代码的情况下,替换或添加新的服务实现,提升系统灵活度。

不难发现,SPI的这些说法中,无一例外的提到了,SPI是一种机制,是一种拓展机制。

这种机制,可以实现动态的添加和替换服务实现,常用在框架和组件的开发中,实现了模块化和可插拔性。

SPI简单使用示例

我们先看个简单的例子,然后再深入JDK的源码看具体是怎么实现的。

整体的代码结构

一个日志工具类,LogUtil,假设他是通过jar包引入的依赖,LogDemo类,是我们实际卡法的代码。

Log是我们用来打日志的接口,jar包中有Log4j,Log4j2和LogBack实现。

Log5MuMu是调用方自己的日志接口实现。

现在想实现如下功能,在接口调用方,可以灵活的配置要使用的日志实现,且可以动态替换成自己的实现Log4MuMu,这里就用到SPI。

代码实现

LogDemo的具体代码

Log接口

LogUtil具体的实现

jar包中的Log接口实现

Log4j实现

LogBack实现

调用方的接口实现

Log4MuMu实现

配置文件

目录是META-INF/services,文件名和要加载的接口的全限定性类名保持一致

配置内容是接口对应实现的全限定性类名

当配置文件配置的是Log4MuMu,执行结果如下

修改配置文件为Log4j

bingo,我们实现了可以在不修改代码的情况下,仅修改配置文件中的类名,就可以动态的替换打印日志的实现类。那么具体是怎么实现的类,接下来我们看看JDK中具体实现SPI的核心源码。

SPI源码解析

下图是SPI的运行机制

JDK中SPI的实现,主要依赖ServiceLoader及其内部类LazyIterator

ServiceLoader类的核心结构

ServiceLoader实现的采用的是懒加载,具体实现在LazyIterator中,在实际迭代中会进行类的加载和实例化对象

LazyIterator类的核心方法

LazyIterator类实现了Iterator接口,采用的懒加载方式,在迭代过程中去记载具体的类和实现。

hasNext方法调用的是hasNextService 方法,用来加载配置文件中的全限定性类名

next方法调用的是nextService方法,用来实例化配置文件中的类,并进行缓存

解析配置文件parse方法

parse方法是解析配置文件的具体实现

JAVA实现的SPI,配置文件要满足下面几条

文件位于META-INF/services/下

文件名就是接口的全限定性类名

配置文件内容是需要加载的配置的实现类的全限定性类名

接口实现按行配置,可以是多个。如果包含#号,每一行只取第一个#号前的内容。

JDBC中加载数据库驱动

以下是JDBC中加载数据库驱动的核心类DriverManager,其主要逻辑loadInitialDrivers方法也是使用了ServiceLoader,来实现数据库驱动的热插拔。

SPI和API的区别

API(Application Programming Interface)和SPI(Service Provider Interface)都是软件设计中用于定义组件间交互的接口,但它们有着不同的定义和用途。

API 是应用程序之间的接口,规定了不同组件之间如何进行功能调用。它提供了一组预定义的类和方法,开发者可以基于这些接口来完成特定的任务或调用功能。API的主要目的是通过标准化接口来实现模块之间的互操作,强调的是调用者和被调用者之间的契约。

SPI 是一种允许服务提供者扩展和替换应用程序的核心部件或功能的接口。它常见于框架和库,允许开发者插入自定义实现。SPI的主要目的是提供扩展点,框架开发者通过定义接口,允许服务提供者实现这些接口,从而在不修改框架核心代码的情况下扩展功能,强调的是实现提供者和框架之间的松耦合。

区别:

针对对象不同:API 通常是面向最终用户或外部系统的,提供了可直接使用的功能;而SPI 更多是面向系统开发者,为他们提供一种将新服务或插件加入系统的方式。

目的不同:API的主要目的是提供接口供外界访问和使用特定的功能或数据;SPI则是为了提供一个标准,允许第三方开发者实现并插入新的服务。

定义方式不同:API 是由开发者主动编写并公开给其他开发者使用的,而 SPI 是由框架或库提供方定义的接口,供第三方开发者实现。

调用方式不同:API 是通过直接调用接口的方法来使用功能,而 SPI 是通过配置文件来指定具体的实现类,然后由框架或库自动加载和调用。

灵活性不同:API 的实现类必须在编译时就确定,无法动态替换;而 SPI 的实现类可以在运行时根据配置文件的内容进行动态加载和替换。

依赖关系不同:API 是被调用方依赖的,即应用程序需要引入 API 所在的库才能使用其功能;而 SPI 是调用方依赖的,即框架或库需要引入第三方实现类的库才能加载和调用。

总的来说,API 和 SPI 都是软件开发中的接口,但API更多是面向外部使用,而SPI则是面向内部扩展。在实践中,一个服务可能同时提供API和SPI,API用于调用服务,而SPI用于扩展服务。

写在最后

SPI到底是什么

SPI全称为Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的文件夹查找文件,自动加载文件里所定义的类并实例化。

SPI的核心思想 - 解耦&OCP

Java SPI实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,核心目的是解耦。

SPI符合程序设计的开闭原则

PS:开闭原则(Open Close Principle),简称OCP,是面向对象设计中重要的原则,它要求软件实体对扩展开放,对修改封闭,软件实体包含函数、类、模块甚至是可执行程序。

JDK中SPI存在的问题

①接口的实现类,全都会被实例化一遍造成资源的浪费

②获取实现类,只能通过Iterator,不够灵活

上面的两个问题在Dubbo的SPI中都进行了解决,后续将出一篇文章进行讲解,敬请期待~~

欢迎大家点赞-评论-关注,关注【程序员木木熊】,了解更多后端技术知识!!

也可以微信搜索程序员木木熊,海量Java、架构、面试、算法资料免费送,拉你进入技术交流社群,共同学习进步~~