java的SPI机制

①SPI的简介

SPI是Service Provider Interfaces的简称。根据Java的SPI规范,我们可以定义一个服务接口,具体的实现由对应的实现者去提供,即Service Provider(服务提供者)。然后在使用的时候只要根据SPI的规范去获取对应的服务提供者的服务实现即可。

②使用场景

一般使用接口的实现类都是静态new一个实现类赋值给接口引用,如下:

HelloService service = new HelloImpl();

如果需要动态的获取一个接口的实现类呢?全局扫描全部的Class,然后判断是否实现了某个接口?代价太大,一般不会这么做。一种合适的方式就是使用配置文件,把实现类名配置在某个地方,然后读取这个配置文件,获取实现类名。

③SPI的作用

这种方式主要是针对不同的服务提供厂商,对不同场景的提供不同的解决方案制定的一套标准,举个简单的例子,如现在的JDK中有支持音乐播放,假设只支持mp3的播放,有些厂商想在这个基础之上支持mp4的播放,有的想支持mp5,而这些厂商都是第三方厂商,如果没有提供SPI这种实现标准,那就只有修改JAVA的源代码了,那这个弊端也是显而易见的,也就是不能够随着JDK的升级而升级现在的应用了,而有了SPI标准,SUN公司只需要提供一个播放接口,在实现播放的功能上通过ServiceLoad的方式加载服务,那么第三方只需要实现这个播放接口,再按SPI标准进行打包成jar,再放到classpath下面就OK了,没有一点代码的侵入性。

④SPI的使用

在实现类的jar包的META-INF下新建一个文件夹services,并在services下新建一个文件,以接口的全限定名为文件名,内容为实现类的全限定名。

实现代码示例:

四个项目:spiInterface、spiA、spiB、spiDemo

spiInterface中定义了一个com.zs.IOperation接口。

spiA、spiB均是这个接口的实现类,服务提供者。

spiDemo作为客户端,引入spiA或者spiB依赖,面向接口编程,通过spi的方式获取具体实现者并执行接口方法。

链接:https://github.com/lemon-simple/SPIDEMO(代码由最后的参考博客提供)

⑤实现SPI机制的核心类:ServiceLoader

ServiceLoader不是一实例化以后立马就去读配置文件中的服务实现者,并且进行对应的实例化工作的,而是会等到需要通过其Iterator实现获取对应的服务提供者时才会加载对应的配置文件进行解析,具体来说是在调用Iterator的hasNext方法时会去加载配置文件进行解析,在调用next方法时会将对应的服务提供者进行实例化并进行缓存。所有的配置文件只加载一次,服务提供者也只实例化一次,如需要重新加载配置文件可调用ServiceLoader的reload方法。

1. 构造函数,如果不指定ClassLoader或者指定的为null,则使用ClassLoader.getSystemClassLoader() ,即AppClassLoader。(设置类加载器:线程上下文类加载器

2. 遍历有两个方法

hasNext会调用hasNextService,如果指定的ClassLoader为空(一般不会为空,构造函数会初始化),则调用ClassLoader.getSystemClassLoader().getResources,否则调用指定的ClassLoader的getResources方法,获取META-INF/services/接口的全限定名称,如META-INF/services/com.test.loader.HelloService,从这个文件中找出实现类全限定名称。next会调用nextService,根据hasNextService获取的实现类信息,使用指定的ClassLoader进行加载和实例化。 

⑥典型实例:JDBC的使用

通常各大厂商(如Mysql、Oracle)会根据一个统一的规范(java.sql.Driver)开发各自的驱动实现逻辑。客户端使用jdbc时不需要去改变代码,直接引入不同的spi接口服务即可。

Mysql的则是com.mysql.jdbc.Drive,Oracle则是oracle.jdbc.driver.OracleDriver。

以jdbc加载driver为例:

数据库连接的伪代码:

 

1. java.sql.DriverManager静态块初始执行,其中使用spi机制加载jdbc具体实现

2.loadInitialDrivers()中完成了引入的数据库驱动的查找以及载入(看到ServiceLoader类就可以和上述内容联系起来了。)

 

 

SPI规范中配置文件的位置指定为:("META-INF/services/";)

接口:(JDK中的接口)

配置文件中的内容为接口实现类的全路径名:(实现类在第三方jar包中)

双亲委派模型的破坏者-线程上下文类加载器

在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为它们提供实现,如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载,而 SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下,由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法,但SPI的核心接口类是由引导类加载器来加载的,而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。在这种情况下,我们就需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器就是很好的选择。

线程上下文类加载器(contextClassLoader)是从 JDK 1.2 开始引入的,我们可以通过java.lang.Thread类中的getContextClassLoader()和 setContextClassLoader(ClassLoader cl)方法来获取和设置线程的上下文类加载器。如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类和资源,如下图所示,以jdbc.jar加载为例

参考博客:https://blog.youkuaiyun.com/lemon89/article/details/79189475

### Java 中的 SPI 机制详解 #### 定义与作用 SPI 是 Service Provider Interface 的缩写,是 JDK 内置的一种服务发现机制。通过 SPI 可以使接口与其具体实现解耦合,在运行时动态加载指定的服务实现[^1]。 #### 工作原理 当应用程序启动时,JVM会扫描 `META-INF/services` 文件夹下的配置文件。对于每一个定义好的接口,都会有一个对应的配置文件来指明具体的实现类名称。例如,如果存在名为 `com.example.LoggerService` 接口,则应在资源路径下创建一个名为 `com.example.LoggerService` 的文件,并在此文件中记录该接口的具体实现类名[^2]。 #### 使用案例分析 考虑如下代码片段: ```java public class TestJavaSPI { public static void main(String[] args) { LoggerService loggerService = LoggerService.getService(); loggerService.info("你好"); loggerService.debug("测试Java SPI 机制"); } } ``` 上述例子展示了如何利用 SPI 加载并调用不同日志框架的日志功能。这里假设 `LoggerService` 是一个抽象出来的用于获取日志服务实例的方法;而实际执行的日志操作则由特定的日志库(比如 Logback)完成。因此可以看到控制台输出了来自Logback的信息级别和调试级别的消息。 #### 实现方式概述 要让某个接口支持 SPI 功能,开发者只需要遵循一定的约定即可: - 创建接口; - 在项目的 `resources/META-INF/services/` 下建立同包名相同命名规则的文字文件; - 文本文件内容即为对应接口实现者的全限定类名列表,每行一条记录[^3]。 #### 关键特性总结 - **灵活性**: 不必硬编码依赖关系,允许外部插件形式接入新的业务逻辑处理单元。 - **可维护性**: 减少了修改源码的需求,便于后期维护和技术栈升级迁移工作。 - **扩展性强**: 支持多版本共存和平滑切换不同的算法策略或工具链组件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值