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

Java SPI机制

目录

什么是spi

API与SPI区别

SPI实现案例

SPI应用场景

1. JDBC场景

2. ShardingSphere场景

3. Spring 场景

4. SLF4J 日志门面 场景


 

Java.util.spi下提供了SPI机制,SPI机制(Service Provider Interface)其实源自服务提供者框架(Service Provider Framework,参考[EffectiveJava]page6),是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。Java SPI 是基于接口的编程+策略模式+约定配置文件组合实现的动态加载机制,能够很方便的为某个接口寻找服务实现的机制。

什么是spi

SPI 全称:Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。

面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候不用在程序里动态指明,这就需要一种服务发现机制。java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。这有点类似IOC的思想,将装配的控制权移到了程序之外。这是一种JDK内置的一种服务发现的机制,用于制定一些规范,实际实现方式交给不同的服务厂商。

0183356161484fef9e9eb6438d7f1e45.png

具有解耦、可拔插、面向接口编程、动态类加载等特点。

当服务的提供者提供了一种接口的实现之后,需要在classpath下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。

spi的不足点

不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。(Spring 的BeanFactory,ApplicationContext 就要高级一些了。)。多个并发多线程使用 ServiceLoader 类的实例是不安全的。

API与SPI区别

API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。从使用人员上来说,API 直接被应用开发人员使用。
SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。

SPI实现案例

public interface AnimalSay {void say();
}

定义一个AnimalManagerLoader

@Data
public class AnimalManagerLoader {private static final AnimalManagerLoader INSTANCE = new AnimalManagerLoader();private final List<AnimalSay> animalSays;private AnimalManagerLoader() {animalSays = load();}/*** 通过SPI加载实现类*/private List<AnimalSay> load() {ArrayList<AnimalSay> animalSays = new ArrayList<>();Iterator<AnimalSay> iterator = ServiceLoader.load(AnimalSay.class).iterator();while (iterator.hasNext()){animalSays.add(iterator.next());}return animalSays;}public static AnimalManagerLoader getInstance() {return INSTANCE;}
}

通过AnimalManagerLoader中的load方法去加载对应的实现类,封装到List集合中,调用如下

public static void main(String[] args) {AnimalManagerLoader animalManagerLoader = AnimalManagerLoader.getInstance();List<AnimalSay> animalSays = animalManagerLoader.getAnimalSays();for (AnimalSay animalSay : animalSays) {animalSay.say();}}

此时提供声音的厂家就需要实现这个接口

/*** 狗狗的声纹*/
public class DogSay implements AnimalSay {public void say() {System.out.println("wang wang ~");}
}/*** 猫咪的声纹*/
public class CatSay implements AnimalSay {@Overridepublic void say() {System.out.println("miao miao ~");}
}

实现类定义了,就需要在 /META-INF/services 中定义一个 com.myjszl.animal.api.AnimalSay文件,内容如下

com.myjszl.dog.api.DogSay
com.myjszl.dog.api.CatSay

SPI应用场景

SPI扩展机制应用场景有很多,比如Common-Logging,JDBC,Dubbo、ShardingSphere等等。

1. JDBC场景

java中定义的java.sql.Driver接口,并没有具体的实现,实现方式而是交给不同的服务厂商:

  1. 在MySQL的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。

  2. PostgreSQL的jar包PostgreSQL-42.0.0.jar中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是PostgreSQL对Java的java.sql.Driver的实现。

2. ShardingSphere场景

在ShardingSphere中为了实现分布式事务提供了一个接口ShardingTransactionManager,但是在其架构中并未对其做出具体的实现,而是交给不同的厂商去实现,比如JTA强一致性事务的XAShardingTransactionManager,在其中META-INF/services就有一个org.apache.shardingsphere.transaction.spi.ShardingTransactionManager文件,如下图:

863083436a8a41fca611362bb53aa014.png

以上只是简单的列举了几个场景,实际应用场景很多,比如Spring、Spring Boot 中都有用到SPI设计。

3. Spring 场景

Spring中大量使用了SPI;比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等

4. SLF4J 日志门面 场景

SLF4J加载不同提供商的日志实现类,比如log4j、log4j2、logback.....

 

相关文章:

  • 【服务器学习】timer定时器模块
  • mac环境使用sudo进行node包管理
  • 矩阵的QR分解
  • sqli-labs关卡18(基于http头部报错盲注)通关思路
  • react 手机端 rc-table列隐藏(根据相关条件是否隐藏)、实现图片上传操作
  • 矩阵的模和内积
  • 基于金鹰算法优化概率神经网络PNN的分类预测 - 附代码
  • 量化交易:借助talib使用技术分析指标
  • Python字典六种类型概述
  • 和解电话(匿名电话)/情侣拉黑联系电话/虚拟号/虚拟中间号/拉黑联系项目代码
  • 直流电机干扰的产生-EMC和EMI
  • Linux的简单使用
  • ATTCK实战系列——红队实战(一)
  • 生成对抗网络(GAN)
  • 云服务器windows service2022 部署git服务器
  • 【Leetcode】104. 二叉树的最大深度
  • Bytom交易说明(账户管理模式)
  • Create React App 使用
  • Docker入门(二) - Dockerfile
  • Java编程基础24——递归练习
  • SwizzleMethod 黑魔法
  • Tornado学习笔记(1)
  • Wamp集成环境 添加PHP的新版本
  • 百度贴吧爬虫node+vue baidu_tieba_crawler
  • 工作手记之html2canvas使用概述
  • 缓存与缓冲
  • 基于axios的vue插件,让http请求更简单
  • 检测对象或数组
  • 深入浏览器事件循环的本质
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 以太坊客户端Geth命令参数详解
  • Mac 上flink的安装与启动
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • #Spring-boot高级
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (C#)一个最简单的链表类
  • (附源码)ssm高校社团管理系统 毕业设计 234162
  • (规划)24届春招和25届暑假实习路线准备规划
  • (十六)串口UART
  • (一)为什么要选择C++
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • .net core 6 集成 elasticsearch 并 使用分词器
  • .net core使用ef 6
  • .NET/C# 获取一个正在运行的进程的命令行参数
  • .NET多线程执行函数
  • .NET中使用Redis (二)
  • .vollhavhelp-V-XXXXXXXX勒索病毒的最新威胁:如何恢复您的数据?
  • @RequestMapping处理请求异常
  • [2018/11/18] Java数据结构(2) 简单排序 冒泡排序 选择排序 插入排序
  • [20180224]expdp query 写法问题.txt
  • [2669]2-2 Time类的定义
  • [52PJ] Java面向对象笔记(转自52 1510988116)
  • [APIO2012] 派遣 dispatching
  • [BZOJ3757] 苹果树
  • [bzoj4010][HNOI2015]菜肴制作_贪心_拓扑排序