Java、Spring、Dubbo三者SPI机制原理与区别

Java、Spring、Dubbo三者SPI机制原理与区别

什么是SPI

SPI全称为Service Provider Interface,是一种动态替换发现的机制,一种解耦非常优秀的思想,SPI可以很灵活的让接口和实现分离,让api提供者只提供接口,第三方来实现,然后可以使用配置文件的方式来实现替换或者扩展,在框架中比较常见,提高框架的可扩展性。

简单来说SPI是一种非常优秀的设计思想,它的核心就是解耦、方便扩展

Java SPI机制–ServiceLoader

ServiceLoader是Java提供的一种简单的SPI机制的实现,Java的SPI实现约定了以下两件事:

  • 文件必须放在META-INF/services/目录底下
  • 文件名必须为接口的全限定名,内容为接口实现的全限定名

这样就能够通过ServiceLoader加载到文件中接口的实现。

来个demo

第一步,需要一个接口及其实现类

public interface LoadBalance {
}public class RandomLoadBalance implements LoadBalance{
}

第二步,在META-INF/services/目录创建一个文件名LoadBalance全限定名的文件,文件内容为RandomLoadBalance的全限定名

image-20240417220053449

内容为实现类全限定名

image-20240417220139791

测试类

public class ServiceLoaderDemo {public static void main(String[] args) {ServiceLoader<LoadBalance> loadBalanceServiceLoader = ServiceLoader.load(LoadBalance.class);for (LoadBalance loadBalance : loadBalanceServiceLoader) {System.out.println("获取到负载均衡策略:" + loadBalance);}}
}

运行结果

image-20240417220312848

在实际的框架设计中,LoadBalance接口定义在框架内部,对于框架的使用者来说,要想自定义LoadBalance实现,嵌入到框架,仅仅只需要写接口的实现和spi文件即可。

实现原理

ServiceLoader中一段核心代码

image-20240417222043382

image-20240417222125291

首先获取一个fullName,就是META-INF/services/接口的全限定名

image-20240417222515865

然后通过ClassLoader获取到资源,接口的全限定名文件对应的资源,然后交给parse方法解析资源

image-20240417222651752

parse方法其实就是通过IO流读取文件的内容,这样就可以获取到接口的实现的全限定名

再后面就是通过反射实例化对象。

ServiceLoader实现原理比较简单,总结起来就是通过IO流读取META-INF/services/接口的全限定名文件的内容,然后反射实例化对象。

优缺点

  1. 浪费资源,例子中只有一个实现类,但是实际情况下可能会有很多实现类,而Java的SPI会全进行实例化,但是这些实现了不一定都用得着,所以就会白白浪费资源。
  2. 无法区分具体的实现,也就是这么多实现类,到底该用哪个实现呢?如果要判断具体使用哪个,只能依靠接口本身的设计,比如接口可以设计为一个策略接口,又或者接口可以设计带有优先级的,但是不论怎样设计,框架作者都得写代码进行判断。

总结来说,ServiceLoader无法做到按需加载或者按需获取某个具体的实现。

使用场景

  • 不需要选择具体的实现,每个被加载的实现都需要被用到
  • 虽然需要选择具体的实现,但是可以通过对接口的设计来解决

Spring SPI机制–SpringFactoriesLoader

Spring提供了一种SPI的实现SpringFactoriesLoader。

Spring的SPI机制的约定如下:

  • 配置文件必须在META-INF/目录下,文件名必须为spring.factories
  • 文件内容为键值对,一个键可以有多个值,只需要用逗号分割就行,同时键值都需要是类的全限定名,键和值可以没有任何类与类之间的关系,当然也可以有实现的关系。

Spring的SPI机制跟Java的不论是文件名还是内容约定都不一样。

来个demo

META-INF/目录下创建spring.factories文件,LoadBalance全限定名为键,RandomLoadBalance全限定名为值

image-20240417224501313

image-20240417224606838

配置类:

image-20240417224834969

测试类:

public class SpringFactoriesLoaderDemo {public static void main(String[] args) {List<LoadBalance> loadBalances = SpringFactoriesLoader.loadFactories(LoadBalance.class, MyConfig.class.getClassLoader());for (LoadBalance loadBalance : loadBalances) {System.out.println("获取到LoadBalance策略:" + loadBalance);}}
}

执行结果

image-20240417224917237

核心原理

image-20240417225522252

image-20240417225700763

跟Java实现的差不多,只不过读的是META-INF/目录下spring.factories文件内容,然后解析出来键值对。

使用场景

Spring的SPI机制在内部使用的非常多,尤其在SpringBoot中大量使用,SpringBoot启动过程中很多扩展点都是通过SPI机制来实现的。

1、自动装配

在SpringBoot3.0之前的版本,自动装配是通过SpringFactoriesLoader来加载的。

image-20240418225803440

但是SpringBoot3.0之后不再使用SpringFactoriesLoader,而是Spring重新从META-INF/spring/目录下的org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中读取了。

image-20240418225907822

2、PropertySourceLoader的加载

PropertySourceLoader用来解析application配置文件

image-20240418230007786

SpringBoot默认提供了 PropertiesPropertySourceLoader 和 YamlPropertySourceLoader两个实现,就是对应properties和yaml文件格式的解析。

image-20240418230147124

SpringBoot在加载PropertySourceLoader时就用了SPI机制

image-20240418230443560

image-20240418230545401

与Java SPI机制对比

  1. Spring的SPI机制对Java的SPI机制对进行了一些简化,Java的SPI每个接口都需要对应的文件,而Spring的SPI机制只需要一个spring.factories文件。
  2. 文件内容,Java的SPI机制文件内容必须为接口的实现类,而Spring的SPI并不要求键值对必须有什么关系,更加灵活。
  3. Spring的SPI机制提供了获取类限定名的方法loadFactoryNames,而Java的SPI机制是没有的。通过这个方法获取到类限定名之后就可以将这些类注入到Spring容器中,用Spring容器加载这些Bean,而不仅仅是通过反射。
  4. Spring的SPI也同样没有实现获取指定某个指定实现类的功能,所以要想能够找到具体的某个实现类,还得依靠具体接口的设计。(依旧无法获取指定实现类,依赖具体接口设计)

PropertySourceLoader就是一个策略接口,当配置文件是properties格式的时候,他可以找到解析properties格式的PropertiesPropertySourceLoader对象来解析配置文件。

Dubbo SPI机制–ExtensionLoader

ExtensionLoader是dubbo的SPI机制的实现类。每一个接口都会有一个自己的ExtensionLoader实例对象,这点跟Java的SPI机制是一样的。

Dubbo的SPI机制也做了以下几点约定:

  • 接口必须要加@SPI注解
  • 配置文件可以放在META-INF/services/META-INF/dubbo/internal/ META-INF/dubbo/META-INF/dubbo/external/这四个目录底下,文件名也是接口的全限定名
  • 内容为键值对,键为短名称(可以理解为spring中Bean的名称),值为实现类的全限定名

来个demo

接口上添加@SPI注解

@SPI
public interface LoadBalance {
}

修改Java的SPI机制测试时配置文件内容,改为键值对,因为Dubbo的SPI机制也可以从META-INF/services/目录下读取文件,所以没重写文件

image-20240421224847010

测试类&运行结果:

public class ExtensionLoaderDemo {public static void main(String[] args) {ExtensionLoader<LoadBalance> extensionLoader = ExtensionLoader.getExtensionLoader(LoadBalance.class);LoadBalance loadBalance = extensionLoader.getExtension("random");System.out.println("获取到random键对应的实现类对象:" + loadBalance);}
}

image-20240421225142120

通过ExtensionLoader的getExtension方法,传入键值对key值,这样就可以精确地找到key值对的实现类。

Dubbo的SPI机制解决了前面提到的无法获取指定实现类的问题。

dubbo核心机制

1、自适应机制

自适应,自适应扩展类的含义是说,基于参数,在运行时动态选择到具体的目标类,然后执行。

每个接口有且只能有一个自适应类,通过ExtensionLoader的getAdaptiveExtension方法就可以获取到这个类的对象,这个对象可以根据运行时具体的参数找到目标实现类对象,然后调用目标对象的方法。

@Adaptive
public class RandomLoadBalance implements LoadBalance{
}

测试一下:

public class ExtensionLoaderDemo {public static void main(String[] args) {ExtensionLoader<LoadBalance> extensionLoader = ExtensionLoader.getExtensionLoader(LoadBalance.class);LoadBalance adaptiveExtension = extensionLoader.getAdaptiveExtension();System.out.println("获取自适应 LoadBalance对象:" + adaptiveExtension);}
}
2、IOC和AOP

Dubbo实现一个轻量级的IOC和AOP的功能。

2.1、依赖注入

Dubbo依赖注入是通过setter注入的方式,注入的对象默认就是上面提到的自适应的对象,在Spring环境下可以注入Spring Bean。

public class RoundRobinLoadBalance implements LoadBalance {private LoadBalance loadBalance;// 设置接口自适应对象public void setLoadBalance(LoadBalance loadBalance) {this.loadBalance = loadBalance;}}

解读:RoundRobinLoadBalance中有一个setLoadBalance方法,参数LoadBalance,在创建RoundRobinLoadBalance的时候,在非Spring环境底下,Dubbo就会找到LoadBalance自适应对象然后通过反射注入。

记得文件中添加键值对:

roundrobin=com.lkl.spi.demo.RoundRobinLoadBalance

2.2、接口回调

Dubbo也提供了一些类似于Spring的一些接口的回调功能,如果类实现了Lifecycle接口,那么创建或者销毁的时候就会回调以下几个方法:

image-20240421230308592

在dubbo3.x的某个版本之后,dubbo提供了更多接口回调,比如说ExtensionPostProcessor、ExtensionAccessorAware,命名跟Spring的非常相似,作用也差不多。

2.3、自动包装

自动包装其实就是aop的功能实现,对目标对象进行代理,并且这个aop功能在默认情况下就是开启的。

在Dubbo中SPI接口的实现中,有一种特殊的类,被称为Wrapper类,这个类的作用就是来实现AOP的。

判断Wrapper类的唯一标准:这个类中必须要有这么一个构造参数,这个构造方法的参数只有一个,并且参数类型就是接口的类型,如下:

public class RoundRobinLoadBalance implements LoadBalance {private final LoadBalance loadBalance;public RoundRobinLoadBalance(LoadBalance loadBalance) {this.loadBalance = loadBalance;}}

此时RoundRobinLoadBalance就是一个Wrapper类。

测试一下

image-20240421232239826

此时,虽然指定了random,但是实际获取到的是RoundRobinLoadBalance,而RoundRobinLoadBalance内部引用了RandomLoadBalance。

如果有很多的包装类,那么就会形成一个责任链条,一个套一个。

所以dubbo的aop跟spring的aop实现是不一样的,spring的aop底层是基于动态代理来的,而dubbo的aop其实算是静态代理,dubbo会自动组装这个代理,形成一条责任链。

默认类

@SPI注解的值对应的实现类

@SPI("random")
public interface LoadBalance {}

此时random这个key对应的实现类就是默认实现,通过getDefaultExtension这个方法就可以获取到默认实现对象。

3、自动激活

所谓的自动激活,就是根据你的入参,动态地选择一批实现类返回给你。

自动激活的实现类上需要加上@Activate注解

@Activate
public interface RandomLoadBalance {}

此时RandomLoadBalance就属于可以被自动激活的类。

获取自动激活类的方法是getActivateExtension,所以根据这个方法的入参,可以动态选择一批实现类。

自动激活这个机制在Dubbo一个核心的使用场景就是Filter过滤器链中。

Filter是dubbo中的一个扩展点,可以在请求发起前或者是响应获取之后就行拦截,作用有点像Spring MVC中的HandlerInterceptor。

image-20240421232932941

如上Filter有很多实现,为了能够区分Filter的实现是作用于provider的还是consumer端,可以用自动激活的机制来根据入参来动态选择一批Filter实现。

比如说ConsumerContextFilter这个Filter就作用于Consumer端。

image-20240421233024604

小结

  • Java的SPI实现比较简单,并没有什么其它功能;
  • Spring得益于自身的ioc和aop的功能,也没有实现太复杂的SPI机制,仅仅是对Java做了一点简化和优化;
  • dubbo的SPI机制为了满足自身框架的使用要求,实现的功能比较多,不仅将ioc和aop的功能集成到SPI机制中,还提供注入自适应和自动激活等功能,还具有获取指定接口实现类的功能。

参考资料

3种SPI机制

需要看的

面试常问的dubbo的spi机制到底是什么?(上)

面试常问的dubbo的spi机制到底是什么?(下)

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用户实践:从 HBase 升级为OceanBase,仟传实现110000 TPS的千亿级KV性能优化

本文作者&#xff1a;仟传网络科技技术专家 刘贵宗 & 肖旺生 一、业务需求及选型背景 仟传网络科技&#xff08;TargetSocial&#xff09;&#xff0c;是国内知名的内容社交平台整合营销服务商&#xff0c;为企业级客户提供高效的KOL&#xff08;关键意见领袖&#xff09;…

牛客周赛 Round 40(A,B,C,D,E,F)

比赛链接 官方讲解 这场简单&#xff0c;没考什么算法&#xff0c;感觉有点水。D是个分组01背包&#xff0c;01背包的一点小拓展&#xff0c;没写过的可以看看&#xff0c;这个分类以及这个题目本身都是很板的。E感觉就是排名放高了导致没人敢写&#xff0c;本质上是个找规律…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之一 简单人脸识别

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之一 简单人脸识别 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之一 简单人脸识别 一、简单介绍 二、简单人脸识别实现原理 三、简单人脸识别案例实现简单步…

【漏洞复现】锐捷 EG易网关 phpinfo.view.php 信息泄露漏洞

0x01 产品简介 锐捷EG易网关是一款综合网关产品&#xff0c;集成了先进的软硬件体系构架&#xff0c;并配备了DPI深入分析引擎、行为分析/管理引擎。这款产品能在保证网络出口高效转发的基础上&#xff0c;提供专业的流控功能、出色的URL过滤以及本地化的日志存储/审计服务。 …

Github首页美化(updating)

Github首页美化 https://github.com/QInzhengk一、新建仓库二、美化Github首页主页访问量统计仓库状态统计常用语言占比统计社交链接 界面展示 https://github.com/QInzhengk 一、新建仓库 对Github首页进行美化&#xff0c;需要新建一个仓库名和自己 Github 用户名相同的仓库…

yolo-驾驶行为监测:驾驶分心检测-抽烟打电话检测

在现代交通环境中&#xff0c;随着汽车技术的不断进步和智能驾驶辅助系统的普及&#xff0c;驾驶安全成为了公众关注的焦点之一 。 分心驾驶&#xff0c;尤其是抽烟、打电话等行为&#xff0c;是导致交通事故频发的重要因素。为了解决这一问题&#xff0c;研究人员和工程师们…

kubernetes部署控制器Deployment

一、概念 在学习rc和rs控制器资源时&#xff0c;这两个资源都是控制pod的副本数量的&#xff0c;但是&#xff0c;他们两个有个缺点&#xff0c;就是在部署新版本pod或者回滚代码的时候&#xff0c;需要先apply资源清单&#xff0c;然后再删除现有pod&#xff0c;通过资源控制&…

南京邮电大学数学实验A答案 | 《MATLAB数学实验》第三版课后习题答案

数学实验A 本仓库收集了2024年我在学习《数学实验A》课程期间完成的作业。课程使用的教材为《MATLAB数学实验》第三版&#xff0c;作者为胡良剑和孙晓君教授。 这个资源库的建立初衷是为了帮助南京邮电大学的同学们在学习过程中有一个参考的依据&#xff0c;减少一些无端浪费…

微机原理实验二、编写一个程序,要求比较两个字符串STRING1和STRING2所含的字符是否相同,相同则显示“MATCH”,若不同则显示“NO MATCH”

微机原理实验二、编写一个程序&#xff0c;要求比较两个字符串STRING1和STRING2所含的字符是否相同&#xff0c;相同则显示“MATCH”&#xff0c;若不同则显示“NO MATCH” 实验目标&#xff1a; 编写一个程序&#xff0c;要求比较两个字符串STRING1和STRING2所含的字符是否相…

数字化校园引领未来

在当今科技日新月异的时代&#xff0c;数字化转型已成为各行各业的必然趋势&#xff0c;教育领域也不例外。随着信息技术的迅猛发展&#xff0c;数字化已经成为推动教育变革的重要力量&#xff0c;它不仅重塑了传统的教育模式&#xff0c;还为学生、教师以及整个教育生态系统带…

什么是ISP,为什么跨境推荐ISP?

ISP&#xff0c;全称Internet Service Provider&#xff0c;即“互联网服务提供商”。它是为个人或企业提供访问、使用或参与互联网服务的组织&#xff0c;主要为用户提供互联网接入业务、信息业务和增值业务。ISP是经国家主管部门批准的正式运营企业&#xff0c;享受国家法律保…

React-hooks: useCallback

useCallback文档 https://react.docschina.org/reference/react/useCallback 是一个允许你在多次渲染中缓存函数的 React Hook。 const cachedFn useCallback(fn, dependencies)参数 fn&#xff1a;想要缓存的函数。此函数可以接受任何参数并且返回任何值。React将会在初次…