SPI和API还在傻傻分不清楚?

news2024/11/30 6:34:43

什么是SPI

介绍

再聊下一个类加载器框架OSGI之前,我们首先学习一下前驱知识SPI

全称:Service Provider Interface

区别于API模式,本质是一种服务接口规范定义权的转移,从服务提供者转移到服务消费者。

怎么理解呢?

API指:服务提供方定义接口规范并按照接口规范完成服务具体实现,消费者需要遵守提供者的规则约束,否则无法消费

SPI指:由服务消费方定义接口规范,服务提供者需要按照消费者定义的规范完成具体实现。否则无法消费。而SPI则是一种callback的思想

所谓callback的思想呢,就是我想让别人跑我的代码,我又不能改别人的代码

SPI从理论上看,是一种接口定义和实现解耦的设计思路,以便于框架的简化和抽象;从实际看,是让服务提供者把接口规范定义权交岀去,至于交给谁是不一定的。这个定义权可以是服务消费者,也可以是任何一个第三方。一旦接口规范定义以后,只有消费者和服务提供者都遵循接口定义,才能匹配消费。

很多人有一些误解,说API就是传统老旧的开发方式和生产关系,SPI就代表了一种新的、更加优秀的架构模式。

其实不是的,两种技术,不明确场景的情况下,没有优劣之分。之所以会产生不同于原有的技术方案,是为了填补原有方案的空缺,而不是取而代之。

两者唯一的差别,在于服务提供者和服务消费者谁更加强势,仅此而已。

举个不恰当的例子:A国是C国工业制成品的消费国,C国只能提供相比A国更具性价比的产品,担心生产的产品会无法在A国销售。这时候,生产者必须遵守A国的生产标准。

谁有主动权,谁就有标准的制定权。在系统架构层面:谁是沉淀通用能力的平台方,谁就是主动权一方。

作用场景

可以定义好一个接口,并且提供多种实现,然后服务调用方可以按照需求调用特定的实现类

太抽象了不好理解没关系,这里用人人都用过的一个例子JDBC 中就用过 SPI 机制

public static Connection getConnection() {
    Connection conn = null;
    try {
        Class.forName("com.mysql.jdbc.Driver");
        try {
            conn = DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD);
            System.out.println(conn);
        } catch (SQLException e) {
            System.out.println("数据库连接失败");
        }
    } catch (ClassNotFoundException e) {
        //System.out.println("驱动包不存在");
    }
    return conn;
}

其实不写注册驱动这一步也可以

public static Connection getConnection() {
    Connection conn = null;
    //try {
        //Class.forName("com.mysql.jdbc.Driver");
        try {
            conn = DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD);
            System.out.println(conn);
        } catch (SQLException e) {
            System.out.println("数据库连接失败");
        }
    //} catch (ClassNotFoundException e) {
        //System.out.println("驱动包不存在");
    //}
    return conn;
}

为什么 JDBC 中不注册驱动还能连接数据库成功?就是因为SPI机制

文件 META-INF/services/java.sql.Driver 内容如下

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

读取这个内容和代码中Class.forName(“com.mysql.jdbc.Driver”);这一句是等价的

是不是有服务提供者需要按照消费者定义的规范完成具体实现这个味了,同理,oracle的jdbc驱动包这个文件对应的内容是

oracle.jdbc.OracleDriver

在这里插入图片描述

这样做有什么好处呢?就是调用方按照自己定义的规范调用服务提供方就行,不需要在意提供方实现细节。

那么在实际生产环境中SPI会有哪些应用场景呢?

例如:跟中台对接的ABC多个业务链路可以用自己行业定制的实现方式

比如假设我做电商网站的退货,标准退货流程中的验货节点

如果是快消行业 货品货值比较低,退货发个地址让客户自行寄回就行,验货节点默认空实现就行

如果是家装行业 货品货值高,而且搬运不便,就要实现工人师傅上门验货,在验货节点就要实现诸如派发工单上门的操作

如果是3c数码行业 货品货值高,而且有可能被换配件或者掉包,那么在验货的时候就需要核对机器sn码,正确的话才能寄回

在中台遇到诸如此类的同一节点不同操作,那么就可以使用SPI机制做不同业务逻辑了,这个思路的集大成者就是实现了SPI机制的OSGI框架的nbf实现。

三种SPI

Java SPI

在这里插入图片描述

Java SPI 是JDK内置的一种服务提供发现机制。 我们一般希望模块直接基于接口编程,调用服务不直接硬编码具体的实现,而是通过为某个接口寻找服务实现的机制,通过它就可以实现,不修改原来jar的情况下, 为 API 新增一种实现。这有点类似 IOC 的思想,将装配的控制权移到了程序之外。 对于 Java 原生 SPI,只需要满足下面几个条件:

1定义服务的通用接口,针对通用的服务接口,提供具体的实现类

2在 src/main/resources/META-INF/services 或者 jar包的 META-INF/services/ 目录中,新建一个文件,文件名为 接口的全名。 文件内容为该接口的具体实现类的全名

3将 spi 所在 jar 放在主程序的 classpath 中

4服务调用方用java.util.ServiceLoader,用服务接口为参数,去动态加载具体的实现类到JVM中,然后就可以正常使用服务了

上面这一大段代码示例如下

1.接口和实现类

接口

public interface DemoService {
    void sayHello();
}

实现类

public class RedService implements DemoService{
    @Override
    public void sayHello() {
        System.out.println("red");
    }
}
public class BlueService implements DemoService{
    @Override
    public void sayHello() {
        System.out.println("blue");
    }
}

2.配置文件

META-INF/services文件夹下,路径名字一定分毫不差写对,配置文件名com.example.demo.spi.DemoService

文件内容

com.example.demo.spi.RedService
com.example.demo.spi.BlueService

3.jar包例如jdbc的需要导入classpath,我们这个示例程序自己写的代码就不用了

4.实际调用

public class ServiceMain {

    public static void main(String[] args) {
        ServiceLoader<DemoService> spiLoader = ServiceLoader.load(DemoService.class);
        Iterator<DemoService> iteratorSpi = spiLoader.iterator();
        while (iteratorSpi.hasNext()) {
            DemoService demoService = iteratorSpi.next();
            demoService.sayHello();
        }
    }
}

调用结果

red
blue

项目如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wzFUbbsN-1669573629440)(https://raw.githubusercontent.com/13884566853/eck-article-imgs/main/img/%E6%9F%90%E6%96%87%E7%AB%A0image-20221128020510796.png)]

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

Java SPI的缺陷

实际上 Java 的 SPI 机制往往被扩展,因为 ServiceLoader 缺少一些有用的特性:

缺少实例的维护,ServiceLoader 每次 load 后,都会生成一份实例,也就是 prototype 无法获取指定的实例,ServiceLoader不像 Spring,只能一次获取所有的接口实例 不支持排序,随着新的实例加入,会出现排序不稳定的情况,作用域没有定义singleton和prototype的定义,不利于用户自由定制

DubboSPI

Dubbo的SPI主要改进了JDK标准的SPI实现:

JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

允许设置别名

如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。

增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。

允许用户扩展实现Filter接口。

基本上讲到这里大家对于SPI可能有个大致的认识,但是要真正理解Dubbo的SPI,还是要仔细看一下源码才可以。

https://www.jianshu.com/p/344c00f8f550https://www.jianshu.com/p/344c00f8f550

别名

@SPI 注解的 value 属性,还可以默认一个“别名”的实现。比如在Dubbo 中,默认的是Dubbo 私有协议:dubbo protocol - dubbo:// 来看看Dubbo中协议的接口:

@SPI("dubbo") 
public interface Protocol { 
    //
} 

在 Protocol 接口上,增加了一个 @SPI 注解,而注解的 value 值为 Dubbo ,通过 SPI 获取实现时就会获取 Protocol SPI 配置中别名为dubbo的那个实现,com.alibaba.dubbo.rpc.Protocol文件如下:

filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper mock=com.alibaba.dubbo.rpc.support.MockProtocol dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol com.alibaba.dubbo.rpc.protocol.http.HttpProtocol com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol rest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol registry=com.alibaba.dubbo.registry.integration.RegistryProtocol qos=com.alibaba.dubbo.qos.protocol.QosProtocolWrapper

然后只需要通过getDefaultExtension,就可以获取到 @SPI 注解上value对应的那个扩展实现了

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getDefaultExtension(); //protocol: DubboProtocol

还有一个 Adaptive 的机制,虽然非常灵活,但……用法并不是很“优雅”,这里就不介绍了

Dubbo 的 SPI 中还有一个“加载优先级”,优先加载内置(internal)的,然后加载外部的(external),按优先级顺序加载,如果遇到重复就跳过不会加载了。

所以如果想靠classpath加载顺序去覆盖内置的扩展,也是个不太理智的做法,原因同上 - 加载顺序不严谨

Spring SPI

Spring 的 SPI 配置文件是一个固定的文件 - META-INF/spring.factories,功能上和 JDK 的类似,每个接口可以有多个扩展实现,使用起来非常简单:

//获取所有factories文件中配置的LoggingSystemFactory 
List<LoggingSystemFactory>> factories = SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader); 

Spring Boot 中 spring.factories 的配置加上常用的mybatis-plus的包路径,Spring就会自动扫描并将mybatis的bean加载进ioc容器中

如果我们要扩展某个接口的话,只需要在你的项目(spring boot)里新建一个META-INF/spring.factories文件,只添加你要的那个配置,不要完整的复制一遍 Spring Boot 的 spring.factories 文件然后修改 比如我只想添加一个新的 LoggingSystemFactory 实现,那么我只需要新建一个META-INF/spring.factories文件,而不是完整的复制+修改:

org.springframework.boot.logging.LoggingSystemFactory=\com.example.log4j2demo.Log4J2LoggingSystem.Factory

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

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

相关文章

JavaScript游戏开发(3)(笔记)

文章目录七、支持移动设备的横向卷轴游戏准备7.1 角色的简单移动7.2 背景7.3 加入敌人与帧数控制7.4 碰撞、计分、重新开始7.5 手机格式7.6 全屏模式7.7 存在的问题附录素材可以去一位大佬放在github的源码中直接下&#xff0c;见附录。 七、支持移动设备的横向卷轴游戏 使用…

手摸手使用IDEA创建多模块(maven)项目

有了前面两篇手摸手打底&#xff0c;相信大家对IDEA创建项目和配置使用maven已经没有什么问题了。那么这篇文章阅读起来也会非常流畅。 对于IDEA来说&#xff0c;可以用拼接模块&#xff08;Module&#xff09;并引用的方式来“搭”一个项目&#xff08;Project&#xff09;。…

【全志T113-S3_100ask】15-2 linux系统gpio模拟spi驱动屏幕——ILI9341

【全志T113-S3_100ask】15-2 linux系统gpio模拟spi驱动屏幕——ILI9341背景&#xff08;一&#xff09;查阅参考文档&#xff08;二&#xff09;使能内核模块&#xff08;三&#xff09;修改设备树&#xff08;四&#xff09;测试&#xff08;五&#xff09;后语背景 在上一小…

nginx的安装与nginx降权+匹配php

安装nginx: 安装的插件的作用: 1.gcc 可以编译 C,C,Ada,Object C和Java等语言&#xff08;安装 nginx 需要先将官网下载的源码进行编译&#xff0c;编译依赖 gcc 环境&#xff09; 2.pcre pcre-devel pcre是一个perl库&#xff0c;包括perl兼容的正则表达式库&#xff0c;ngin…

样本于抽样分布(1)-基本概念

从理论上讲&#xff0c;只要对随机现象进行足够多次的试验&#xff0c;被研究的随机现象的规律 性就能清楚地呈现出来&#xff0e; 但实际上&#xff0c;试验的次数只能是有限的&#xff0c;有时甚至是很少 的&#xff0c;因为采集某些数据时&#xff0c;常要将研究的对象破坏&…

【Gradle】四、使用Gradle创建SringBoot微服务项目

使用Gradle创建SringBoot微服务项目一、 创建Springboot项目0、阿里云脚手架创建项目1、引入 t springboot2 、引入依赖3、执行 geradle bootRun 指令4、spring-boot-gradle-plugin 插件‘二、多模块项目1、settings.gradle2、build.gradle3、version.gradle4、test_service的配…

图论:自反与对称

图论1.自反与反自反2.对称与反对称3.传递与非传递1.自反与反自反 自反&#xff1a;相同顶点都在集合内。 反自反&#xff1a;相同顶点都不在集合内。 参考下图&#xff1a;有三部分&#xff0c;红色的自反&#xff0c;蓝色的反自反&#xff0c;以及白色的都不是。 例1&#…

Animation

1、Animation窗口 Window——>Animation——>Animation Animation窗口 直译就是动画窗口&#xff0c;它主要用于在Unity内部创建和修改动画&#xff0c;所有在场景中的对象都可以通过Animation窗口为其制作动画 原理&#xff1a; 制作动画时&#xff1a;记录在固定时间…

[Linux]-----进程信号

文章目录前言一、什么是信号我们是如何得知这些信号呢&#xff1f;我们知道对应的信号产生时&#xff0c;要做什么呢&#xff1f;二、进程信号前台进程和后台进程注意三、信号列表信号的捕捉四、信号产生前用户层产生信号的方式signal函数killraiseabort由软件条件产生信号硬件…

【Java】才疏学浅·小石Java问道之路

大家好&#xff0c;这里是喜爱编程&#xff0c;又热爱生活的小石~———— ————个人情报昵称&#xff1a;小石&#xff08;起源于姓氏啦~&#xff09;破壳日&#xff1a;4月12日身高&#xff1a;1 m ↑技术基础&#xff1a;c node.js mysql 爱好&#xff1a;上网冲浪 听…

【学习笔记45】JavaScript的分页效果

highlight: an-old-hope 一、分页效果 (一) 首次打开页面 从数组内截取部分数据展示调整页码信息为&#xff1a;当前页 / 总页码处理按钮 3.1 如果当前在第一页&#xff0c;禁用上一页按钮(添加类名disable)3.2 如果当前页在最后一页(当前页 总页码),禁用下一页按钮(添加类名…

SpringCloud微服务实践之六 Feign最佳实践(抽取)

传统Feign面临的问题&#xff1a; 1、每个子项目都要写所要调用服务的pojo 2、每个子项目都要写所要调用服务的feign client客户端 优化思路&#xff1a;由提供服务服务的子项目统一归集代码&#xff0c;统一对外提供接口服务、Feign子项目统一管理服务远程调用、 将FeignClien…

【菜菜的sklearn课堂笔记】逻辑回归与评分卡-用逻辑回归制作评分卡-分箱

视频作者&#xff1a;菜菜TsaiTsai 链接&#xff1a;【技术干货】菜菜的机器学习sklearn【全85集】Python进阶_哔哩哔哩_bilibili 分训练集和测试集 from sklearn.model_selection import train_test_split X pd.DataFrame(X) y pd.DataFrame(y)Xtrain,Xtest,Ytrain,Ytest …

DSP-FIR滤波器设计

目录 Gibbs现象:用三角函数逼近间断点: Gibbs现象特点: 常见窗函数&#xff1a; 窗函数的主要频谱参数: 矩形窗(Rectangular)&#xff1a; 汉宁窗(Hanning)&#xff1a; 汉明窗(Hamming)&#xff1a; 布莱克曼窗(Blackman)&#xff1a; 窗函数之间的性能对比&#xff…

Script file ‘F:.....\pip-script.py‘ is not present 原因及解决办法

一 报错类型 二 原因 可能我们使用pip install --upgrade pip或者conda安装一下包时因为网络原因导致只是卸载旧版本而未安装。 三 解决策略 3.1 Anaconda 切换到你的anaconda安装目录并进入Scripts文件夹内(D:\Apps\anaconda3\Scripts) 运行以下代码&#xff1a; conda i…

【Kafka】Kafka基础架构及相关概念

文章目录前言一、Kafka基础知识二、Kafka分区副本参考前言 在以前的定义中&#xff0c;Kafka被定义为一个分布式的基于发布/订阅模式的消息队列&#xff08;Message Queue&#xff09;&#xff0c;主要应用于大数据实时处理领域&#xff0c;类似的产品主要有ActiveMQ、RabbitM…

flutter useRootNavigator属性的作用

useRootNavigator 用于确定是否将对话框推到距给定上下文最远或最接近给定上下文的Navigator 问题&#xff1a; 在使用showDatePicker的时候&#xff0c;android手机如果侧滑返回的时候&#xff0c;页面会关闭&#xff0c;showDatePicker弹出的dailog缺没有关闭。 使用如下…

【学习笔记42】操作DOM

操作DOM一、操作DOM1、步骤2、创建元素节点3、创建文本节点4、增加dom(添加到指定父节点的最后)5、增加dom(添加到指定父节点的最后)6、增加dom(添加到父节点的最前边)7、删除DOM8、修改某一个节点二、克隆DOM1、说明2、复制(克隆)一个LI三、获取元素尺寸(占地面积)四、获取浏览…

WordPress怎么禁止用户使用HTML标签,自动过滤HTML代码?

WordPress怎么禁止用户使用HTML标签&#xff0c;自动过滤HTML代码&#xff1f;出于安全考虑WordPress默认禁止角色为作者的用户写文章时直接添加HTML代码&#xff0c;包括读者留言时也是不允许的。如果想开放此限制&#xff0c;允许作者撰写文章和读者留言时添加HTML代码&#…

Java项目——博客系统(前后端交互)

项目展示 项目说明 使用servlet&#xff0c;实现前后端交互&#xff0c;通过jdbc和mysql数据库进行数据传输&#xff0c;使得可以将用户信息和博客列表存储在数据库中&#xff0c;实现真正的服务器&#xff0c;客户端&#xff0c;数据库三者的交互 项目代码 数据库 在sql数…