某大厂面试题:说一说Java、Spring、Dubbo三者SPI机制的原理和区别
大家好,我是三友~~
今天来跟大家聊一聊Java、Spring、Dubbo三者SPI机制的原理和区别。
其实我之前写过一篇类似的文章,但是这篇文章主要是剖析dubbo的SPI机制的源码,中间只是简单地介绍了一下Java、Spring的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的全限定名
测试类:
public class ServiceLoaderDemo {
public static void main(String[] args) {
ServiceLoader<LoadBalance> loadBalanceServiceLoader = ServiceLoader.load(LoadBalance.class);
Iterator<LoadBalance> iterator = loadBalanceServiceLoader.iterator();
while (iterator.hasNext()) {
LoadBalance loadBalance = iterator.next();
System.out.println("获取到负载均衡策略:" + loadBalance);
}
}
}
测试结果:
此时就成功获取到了实现。
在实际的框架设计中,上面这段测试代码其实是框架作者写到框架内部的,而对于框架的使用者来说,要想自定义LoadBalance实现,嵌入到框架,仅仅只需要写接口的实现和spi文件即可。
实现原理
如下是ServiceLoader中一段核心代码
首先获取一个fullName,其实就是META-INF/services/接口的全限定名
然后通过ClassLoader获取到资源,其实就是接口的全限定名文件对应的资源,然后交给parse
方法解析资源
parse
方法其实就是通过IO流读取文件的内容,这样就可以获取到接口的实现的全限定名
再后面其实就是通过反射实例化对象,这里就不展示了。
所以其实不难发现ServiceLoader实现原理比较简单,总结起来就是通过IO流读取META-INF/services/接口的全限定名
文件的内容,然后反射实例化对象。
优缺点
由于Java的SPI机制实现的比较简单,所以他也有一些缺点。
第一点就是浪费资源,虽然例子中只有一个实现类,但是实际情况下可能会有很多实现类,而Java的SPI会一股脑全进行实例化,但是这些实现了不一定都用得着,所以就会白白浪费资源。
第二点就是无法对区分具体的实现,也就是这么多实现类,到底该用哪个实现呢?如果要判断具体使用哪个,只能依靠接口本身的设计,比如接口可以设计为一个策略接口,又或者接口可以设计带有优先级的,但是不论怎样设计,框架作者都得写代码进行判断。
所以总得来说就是ServiceLoader无法做到按需加载或者按需获取某个具体的实现。
使用场景
虽然说ServiceLoader可能有些缺点,但是还是有使用场景的,比如说:
不需要选择具体的实现,每个被加载的实现都需要被用到 虽然需要选择具体的实现,但是可以通过对接口的设计来解决
Spring SPI机制--SpringFactoriesLoader
Spring我们都不陌生,他也提供了一种SPI的实现SpringFactoriesLoader。
Spring的SPI机制的约定如下:
配置文件必须在 META-INF/
目录下,文件名必须为spring.factories文件内容为键值对,一个键可以有多个值,只需要用逗号分割就行,同时键值都需要是类的全限定名,键和值可以没有任何类与类之间的关系,当然也可以有实现的关系。
所以也可以看出,Spring的SPI机制跟Java的不论是文件名还是内容约定都不一样。
来个demo
在META-INF/
目录下创建spring.factories文件,LoadBalance为键,RandomLoadBalance为值
测试:
public class SpringFactoriesLoaderDemo {
public static void main(String[] args) {
List<LoadBalance> loadBalances = SpringFactoriesLoader.loadFactories(LoadBalance.class, MyEnableAutoConfiguration.class.getClassLoader());
for (LoadBalance loadBalance : loadBalances) {
System.out.println("获取到LoadBalance对象:" + loadBalance);
}
}
}
运行结果:
成功获取到了实现对象。
核心原理
如下是SpringFactoriesLoader中一段核心代码
其实从这可以看出,跟Java实现的差不多,只不过读的是META-INF/
目录下spring.factories文件内容,然后解析出来键值对。
使用场景
Spring的SPI机制在内部使用的非常多,尤其在SpringBoot中大量使用,SpringBoot启动过程中很多扩展点都是通过SPI机制来实现的,这里我举两个例子
1、自动装配
在SpringBoot3.0之前的版本,自动装配是通过SpringFactoriesLoader来加载的。
但是SpringBoot3.0之后不再使用SpringFactoriesLoader,而是Spring重新从META-INF/spring/
目录下的org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中读取了。
至于如何读取的,其实猜也能猜到就跟上面SPI机制读取的方式大概差不多,就是文件路径和名称不一样。
2、PropertySourceLoader的加载
PropertySourceLoader是用来解析application配置文件的,它是一个接口
SpringBoot默认提供了 PropertiesPropertySourceLoader 和 YamlPropertySourceLoader两个实现,就是对应properties和yaml文件格式的解析。
SpringBoot在加载PropertySourceLoader时就用了SPI机制
与Java SPI机制对比
首先Spring的SPI机制对Java的SPI机制对进行了一些简化,Java的SPI每个接口都需要对应的文件,而Spring的SPI机制只需要一个spring.factories文件。
其次是内容,Java的SPI机制文件内容必须为接口的实现类,而Spring的SPI并不要求键值对必须有什么关系,更加灵活。
第三点就是Spring的SPI机制提供了获取类限定名的方法loadFactoryNames
,而Java的SPI机制是没有的。通过这个方法获取到类限定名之后就可以将这些类注入到Spring容器中,用Spring容器加载这些Bean,而不仅仅是通过反射。
但是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
首先在LoadBalance接口上@SPI注解
@SPI
public interface LoadBalance {
}
然后,修改一下Java的SPI机制测试时配置文件内容,改为键值对,因为Dubbo的SPI机制也可以从META-INF/services/
目录下读取文件,所以这里就没重写文件
random=com.sanyou.spi.demo.RandomLoadBalance
测试类:
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);
}
}
通过ExtensionLoader的getExtension
方法,传入短名称,这样就可以精确地找到短名称对的实现类。
所以从这可以看出Dubbo的SPI机制解决了前面提到的无法获取指定实现类的问题。
测试结果:
dubbo的SPI机制除了解决了无法获取指定实现类的问题,还提供了很多额外的功能,这些功能在dubbo内部用的非常多,接下来就来详细讲讲。
dubbo核心机制
1、自适应机制
自适应,自适应扩展类的含义是说,基于参数,在运行时动态选择到具体的目标类,然后执行。
每个接口有且只能有一个自适应类,通过ExtensionLoader的getAdaptiveExtension
方法就可以获取到这个类的对象,这个对象可以根据运行时具体的参数找到目标实现类对象,然后调用目标对象的方法。
举个例子,假设上面的LoadBalance有个自适应对象,那么获取到这个自适应对象之后,如果在运行期间传入了random
这个key,那么这个自适应对象就会找到random
这个key对应的实现类,调用那个实现类的方法,如果动态传入了其它的key,就路由到其它的实现类。
自适应类有两种方式产生,第一种就是自己指定,在接口的实现类上加@Adaptive注解,那么这个这个实现类就是自适应实现类。
@Adaptive
public class RandomLoadBalance implements LoadBalance{
}
除了自己代码指定,还有一种就是dubbo会根据一些条件帮你动态生成一个自适应类,生成过程比较复杂,这里就不展开了。
自适应机制在Dubbo中用的非常多,而且很多都是自动生成的,如果你不知道Dubbo的自适应机制,你在读源码的时候可能都不知道为什么代码可以走到那里。。
2、IOC和AOP
一提到IOC和AOP,立马想到的都是Spring,但是IOC和AOP并不是Spring特有的概念,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自适应对象然后通过反射注入。
这种方式在Dubbo中也很常见,比如如下的一个场景
RegistryProtocol中会注入一个Protocol,其实这个注入的Protocol就是一个自适应对象。
2.2、接口回调
Dubbo也提供了一些类似于Spring的一些接口的回调功能,比如说,如果你的类实现了Lifecycle接口,那么创建或者销毁的时候就会回调以下几个方法
在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类。
当通过random
获取RandomLoadBalance目标对象时,那么默认情况下就会对RandomLoadBalance进行包装,真正获取到的其实是RoundRobinLoadBalance对象,RoundRobinLoadBalance内部引用的对象是RandomLoadBalance。
测试一下
在配置文件中加入
roundrobin=com.sanyou.spi.demo.RoundRobinLoadBalance
测试结果
从结果可以看出,虽然指定了random
,但是实际获取到的是RoundRobinLoadBalance,而RoundRobinLoadBalance内部引用了RandomLoadBalance。
如果有很多的包装类,那么就会形成一个责任链条,一个套一个。
所以dubbo的aop跟spring的aop实现是不一样的,spring的aop底层是基于动态代理来的,而dubbo的aop其实算是静态代理,dubbo会帮你自动组装这个代理,形成一条责任链。
到这其实我们已经知道,dubbo的spi接口的实现类已经有两种类型了:
自适应类 Wrapper类
除了这两种类型,其实还有一种,叫做默认类,就是@SPI
注解的值对应的实现类,比如
@SPI("random")
public interface LoadBalance {
}
此时random
这个key对应的实现类就是默认实现,通过getDefaultExtension
这个方法就可以获取到默认实现对象。
3、自动激活
所谓的自动激活,就是根据你的入参,动态地选择一批实现类返回给你。
自动激活的实现类上需要加上Activate
注解,这里就又学习了一种实现类的分类。
@Activate
public interface RandomLoadBalance {
}
此时RandomLoadBalance就属于可以被自动激活的类。
获取自动激活类的方法是getActivateExtension
,所以根据这个方法的入参,可以动态选择一批实现类。
自动激活这个机制在Dubbo一个核心的使用场景就是Filter过滤器链中。
Filter是dubbo中的一个扩展点,可以在请求发起前或者是响应获取之后就行拦截,作用有点像Spring MVC中的HandlerInterceptor。
如上Filter有很多实现,所以为了能够区分Filter的实现是作用于provider的还是consumer端,所以就可以用自动激活的机制来根据入参来动态选择一批Filter实现。
比如说ConsumerContextFilter这个Filter就作用于Consumer端。
最后,这里并没有对dubbo的SPI机制进行源码分析,感兴趣的同学可以看一下面试常问的dubbo的spi机制到底是什么?(上)和 面试常问的dubbo的spi机制到底是什么?(下)两篇文章。
总结
通过以上分析可以看出,实现SPI机制的核心原理就是通过IO流读取指定文件的内容,然后解析,最后加入一些自己的特性。
最后总的来说,Java的SPI实现的比较简单,并没有什么其它功能;Spring得益于自身的ioc和aop的功能,所以也没有实现太复杂的SPI机制,仅仅是对Java做了一点简化和优化;但是dubbo的SPI机制为了满足自身框架的使用要求,实现的功能就比较多,不仅将ioc和aop的功能集成到SPI机制中,还提供注入自适应和自动激活等功能。
最后的最后,安利一下大鹏的《保你平安》这部电影,周末去看了,从整体来看我感觉还可以,电影中融入了一些喜剧、动作、恐怖等元素,虽然有些地方强行煽情,但是大鹏想要表达的整体思想还是很值得一看滴。
往期热门文章推荐
扫码或者搜索关注公众号 三友的java日记 ,及时干货不错过,公众号致力于通过画图加上通俗易懂的语言讲解技术,让技术更加容易学习,回复 面试 即可获得一套面试真题。
某大厂面试题:说一说Java、Spring、Dubbo三者SPI机制的原理和区别的更多相关文章
- 一天十道Java面试题----第五天(spring的事务传播机制------>mybatis的优缺点)
这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 41.spring的事务传播机制 42 .spring事务什么时候会失效 43 .什么的是bean的自动装配.有哪些方式? ...
- 一文搞懂Java/Spring/Dubbo框架中的SPI机制
几天前和一位前辈聊起了Spring技术,大佬突然说了SPI,作为一个熟练使用Spring的民工,心中一紧,咱也不敢说不懂,而是在聊完之后赶紧打开了浏览器,开始的学习之路,所以也就有了这篇文章.废话不多 ...
- 大厂面试题系列:重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分
面试题:重载(Overload)和重写(Override)的区别.重载的方法能否根据返回类型进行区分 面试官考察点猜想 这道题纯粹只是考查基础理论知识,对实际开发工作中没有太多的指导意义,毕竟编辑器都 ...
- Java是如何实现自己的SPI机制的? JDK源码(一)
注:该源码分析对应JDK版本为1.8 1 引言 这是[源码笔记]的JDK源码解读的第一篇文章,本篇我们来探究Java的SPI机制的相关源码. 2 什么是SPI机制 那么,什么是SPI机制呢? SPI是 ...
- 就目前市面上的面试整理来说,最全的BAT大厂面试题整理在这
原标题:就目前市面上的面试整理来说,最全的BAT大厂面试题整理在这 又到了面试求职高峰期,最近有很多网友都在求大厂面试题.正好我之前电脑里面有这方面的整理,于是就发上来分享给大家. 这些题目是网友去百 ...
- 多次面试被拒,‘宅家苦修’30天,终获美团offer(含字节跳动/阿里/腾讯等大厂面试题整理)
背景:双非渣本. 今年由于疫情,上半年一直在家里.2月份本来无忧无虑,呆在家里不给国家添乱的时候,发现身边的同学找到了大厂的offer.心里开始有点慌张.本来想在3月份如果能回到学校,就开始考研之路, ...
- java面试题—精选30道Java笔试题解答(二)
摘要: java面试题-精选30道Java笔试题解答(二) 19. 下面程序能正常运行吗() public class NULL { public static void haha(){ System ...
- 最有价值的50道java面试题 适用于准入职Java程序员
下面的内容是对网上原有的Java面试题集及答案进行了全面修订之后给出的负责任的题目和答案,原来的题目中有很多重复题目和无价值的题目,还有不少的参考答案也是错误的,修改后的Java面试题集参照了JDK最 ...
- 面试题:各大公司Java后端开发面试题总结 已看1 背1 有用 链接有必要看看
ThreadLocal(线程变量副本) --整理 Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量. 采用空间换时间,它用于线程间的数据隔离,为每一个 ...
- 一道面试题引发的对 Java 内存模型的一点疑问
一道面试题引发的对Java内存模型的一点疑问 问题描述 如上图所示程序,按道理,子线程会通过 num++ 操作破坏 while 循环的条件,从而终止循环,执行最后的输出操作.但在我的多次运行中,偶尔会 ...
随机推荐
- SSIS Package Version
当 SSIS 首次出现时,有大量关于所有问题的笑话和帖子,以及每个人如何认为 DTS 更好,他们真的必须转换吗?多年来,我开始欣赏 SSIS.它是一个非常强大和有用的工具,可以做一些了不起的事情.当然 ...
- developer mode
PS C:\Windows\system32> reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersio ...
- 大唐电信AC集中管理平台弱口令漏洞
网络资产搜索: 找到平台 进行默认口令登入:admin/1***** 登陆 End!!!
- go 单测
1.在需要测试的go文件同级目录下创建${需要测试的文件名}_test.go文件 2. err, info :函数的返回值 "account-base-dd",1:传进去的参数 f ...
- 升级adb
adb 是没有自动升级的命令的,如果想要更新adb的版本,需要在网上找到自己想要的版本进行更新. 为什么要更新呢? 肯定是在使用中遇到了什么问题必须升级版本才能解决,如果不影响使用,那都无所谓.这里提 ...
- 一个分布式websocket的实现
前情提要 之前我的项目里有一个合作编辑的功能,多个客户端的用户可以合作写一篇文章的不同部分,而且合作的任意作者互相还可以进行文字通讯.这种需求肯定是首选websocket了,因为服务器需要主动给客户端 ...
- 音速启动 Vstart 5.7 win10手动移除后台设置主页
Vstart 用了快12年了,用Wireshark跟踪确实会访问广告页面,也会去上传数据.还会悄悄设置主页 ,所以在Win10上老是被干掉 也想着换 CLaunch 确实不喜欢. Rolan 买了一年 ...
- 中国台湾BSMI认证变动
China Taiwan 中国台湾 2022 年 11 月 1 日,BSMI和经济部发布了针对 18 种音像产品的修订法定检验要求. 自发布之日起,CNS 15598-1:2020 Audio/vid ...
- 机制设计原理与应用(三)Screening
目录 3 Screening 3.1 为单个不可分割的项目定价 3.1.1 对\(\theta\)的假设 3.1.2 问题描述 3.1.3 特性 3.2 为无限可分的项目定价 3.2.1 对\(\th ...
- Springboot中@Autowired为何获取了我们没有注入的Bean?
Springboot中@Autowired为何获取了我们没有注入的Bean? 在做仿牛客网项目的时候,有这样一段话: @Autowired private TemplateEngine templat ...