当前位置: 首页 > news >正文

Java SPI 原理、样例

在 Java 中,SPI(Service Provider Interface)全称为服务提供者接口,它是一种用于实现框架扩展和插件化的机制。

一、SPI 作用

允许在运行时动态地为接口查找服务实现,而不需要在代码中显式地指定具体的实现类。

这使得框架具有更好的可扩展性,第三方可以通过实现特定的接口来为框架提供额外的功能。这在很多框架和库中都有广泛的应用,例如数据库驱动、日志框架等。

二、基本原理

1、定义服务接口:首先定义一个接口或抽象类,这是服务的规范。
2、实现服务接口:编写接口的具体实现类。
3、注册服务实现:在 META-INF/services 目录下创建一个以接口全限定名为文件名的文件,并在该文件中列出所有实现类的全限定名。
4、加载服务实现:使用 ServiceLoader 类来加载和使用服务实现。

三、代码样例

1、定义服务接口

假设现在有一个权威机构,比如 Java,它需要对数据存储进行规范。

它定义了一个数据存储接口,和一个加载实现类的工具类。

package com.storage.specification;/*** 数据存储接口*/
public interface StorageProvider {void save(String data);
}
---------------------------------------------------
package com.storage.specification.spi;import com.storage.specification.StorageProvider;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;/*** 加载 存储服务 工具*/
public class ProviderLoader {public static StorageProvider getStorageProvider(ClassLoader classLoader) {// 从 resources/META-INF/services 获取实现类名称,然后从类路径下加载 实现类ServiceLoader<StorageProvider> loader = ServiceLoader.load(StorageProvider.class, classLoader);Iterator<StorageProvider> iterator = loader.iterator();List<StorageProvider> providers = new ArrayList<>();while (iterator.hasNext()) {providers.add(iterator.next());}return providers.get(0);}
}

 步骤:

1)用IDEA 新建一个 maven 项目,取名叫 storage-specification

72210ef906724b369f01d2e4e93435c1.png

2)新建上述两个类

ef918c0b36394b809b2bf1b165f76832.png

3)install 项目到本地仓库,以便后面项目可以用 gav 坐标引用到

3f98b8a6f289464a86ba527dbecfacf3.png

2、实现服务接口

 现在有两个供应商 Mysql 和 Redis,它们拿到 Java 给的规范,分别提供他们的 二进制文件 存储实现和内存数据库实现。

Mysql 实现:

1)用IDEA 新建一个 maven 项目,取名叫 mysql-storage-provider

ce6ec51b40b84682bb9cf38d585bbeba.png

2)pom 文件依赖 存储规范 gav

d659cb7e4fe64bfa974a7c4a1aeb8ef5.png

3) 编写实现接口

package com.mysql.storage;import com.storage.specification.StorageProvider;public class MysqlStorageProvider implements StorageProvider {@Overridepublic void save(String s) {System.out.println("mysql save " + s);}
}

4)在项目 resources/META-INF/services 目录下

创建一个以接口全限定名 

com.storage.specification.StorageProvider 为文件名的文件(注意不要带后缀),

并在该文件中列出所有实现类的全限定名 com.mysql.storage.MysqlStorageProvider

83c56e97667248b4802bbde0f36f0013.png

5)install 项目到本地仓库,以便后面项目可以用 gav 坐标引用到

422d9523acff44959d660b62988bc35f.png

 以同样的方式创建一个 Redis 项目,取名 redis-storage-provider

576d491552644239b74f4243976001cb.png

区别只在

1)实现类不同

package com.redis.storage;import com.storage.specification.StorageProvider;public class RedisStorageProvider implements StorageProvider {@Overridepublic void save(String s) {System.out.println("redis save " + s);}
}

2) 实现类的全限定名不同

f9505b6029a4405ebff457d9ce1d567d.png

3)同样 install 项目到本地仓库,以便后面项目可以用 gav 坐标引用到

8a66d82e0ea14976b27b0ea2c2cfbdfa.png

3、定义服务使用者

1)用 IDEA 新建一个 maven 项目,取名叫 storage-user

b92732ff51ce4073879f42fdd7b8bada.png

2)pom 依赖引入 规范 gav 和 mysql gav

<dependencies><dependency><groupId>com.storage.specification</groupId><artifactId>storage-specification</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.mysql.storage</groupId><artifactId>mysql-storage-provider</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>

 3) 调用 mysql 存储

0e235b38189546b28c369ee0e7627049.png

4)切换 redis 依赖

<dependencies><dependency><groupId>com.storage.specification</groupId><artifactId>storage-specification</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.redis.storage</groupId><artifactId>redis-storage-provider</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>

 5)调用 redis 存储

可以看到,我们只修改了依赖的实现包,并没有修改代码,就实现了服务的替换。

而且我们的代码中并没有显示地调用服务的实现类。

355d9b822bfa4533b64df3954ee6ef98.png

四、总结

1、SPI 是 JAVA 提供给我们的一种用于实现框架扩展和插件化的机制。它可以让我们在不修改代码的时候,很轻松地替换服务的实现。

2、Spring 有自己的 SPI,Spring 的 SPI 路径是 resources/META-INF/spring.factories。

3、SPI 机制在框架集成中很常见。像 spring-web 使用的 javax.validation 就是通过 SPI 的方式集成了 HibernateValidator 实现。


http://www.mrgr.cn/news/38774.html

相关文章:

  • 基于Python的人工智能应用案例系列(14):Fashion MNIST图像分类CNN
  • 八段锦之养生功效:AI语义学分析
  • 进程管理工具:非daemon进程管理工具supervisor
  • 物理加密机的高性能操作
  • AutoGen框架进行多智能体协作—AI Agentic Design Patterns with AutoGen(一)
  • 【Linux】修改用户名用户家目录
  • C# Blazor Server 调用海康H5Player播放摄像头画面
  • 带链的队列,入队,退队,检测带链队列的状态
  • 虚拟环境更改gcc、g++编译器版本
  • 【经验技巧】如何做好S参数的仿测一致性
  • WeChat_DevTools 断点调试方法总结
  • 工业制氮机在食品行业的应用优势
  • 【JS】封装针对dom节点的全屏查看功能
  • 阿布量化:基于 Python 的量化交易框架
  • 嵌入式学习——进程间通信方式(4)—— 消息队列
  • Ruby 多线程
  • 基于baidu的云函数实现隐藏c2真实地址
  • 助农扶贫微信小程序+ssm论文ppt源码调试讲解
  • 服务器分类极简理解
  • Kafka快速实战与基本原理详解