版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
JDK/Dubbo/Spring三种SPI机制,谁更好?SPI全称为ServiceProviderInterface,是一种服务发现机制。SPI的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过SPI机制为我们的程序提供拓展功能。SPI有什么用?举个栗子,现在我们设计了一款全新的日志框架:super-logger。默认以XML文件作为我们这款日志的配置文件,并设计了一个配置文件解析的接口:package
com.github.kongwu.spisamples;
public
interface
SuperLoggerConfiguration
{
void
configure(String
configFile);
}然后来一个默认的XML实现:package
com.github.kongwu.spisamples;
public
class
XMLConfiguration
implements
SuperLoggerConfiguration{
public
void
configure(String
configFile){
......
}
}那么我们在初始化,解析配置时,只需要调用这个XMLConfiguration来解析XML配置文件即可。package
com.github.kongwu.spisamples;
public
class
LoggerFactory
{
static
{
SuperLoggerConfiguration
configuration
=
new
XMLConfiguration();
configuration.configure(configFile);
}
public
static
getLogger(Class
clazz){
......
}
}这样就完成了一个基础的模型,看起来也没什么问题。不过扩展性不太好,因为如果想定制/扩展/重写解析功能的话,我还得重新定义入口的代码,LoggerFactory也得重写,不够灵活,侵入性太强了。比如现在用户/使用方想增加一个yml文件的方式,作为日志配置文件,那么只需要新建一个YAMLConfiguration,实现SuperLoggerConfiguration就可以。但是……怎么注入呢,怎么让LoggerFactory中使用新建的这个YAMLConfiguration?难不成连LoggerFactory也重写了?如果借助SPI机制的话,这个事情就很简单了,可以很方便的完成这个入口的扩展功能。下面就先来看看,利用JDK的SPI机制怎么解决上面的扩展性问题。JDKSPIJDK中提供了一个SPI的功能,核心类是java.util.ServiceLoader。其作用就是,可以通过类名获取在"META-INF/services/"下的多个配置实现文件。为了解决上面的扩展问题,现在我们在META-INF/services/下创建一个com.github.kongwu.spisamples.SuperLoggerConfiguration文件(没有后缀)。文件中只有一行代码,那就是我们默认的com.github.kongwu.spisamples.XMLConfiguration(注意,一个文件里也可以写多个实现,回车分隔)META-INF/services/com.github.kongwu.spisamples.SuperLoggerConfiguration:
com.github.kongwu.spisamples.XMLConfiguration然后通过ServiceLoader获取我们的SPI机制配置的实现类:ServiceLoader
serviceLoader
=
ServiceLoader.load(SuperLoggerConfiguration.class);
Iterator
iterator
=
serviceLoader.iterator();
SuperLoggerConfiguration
configuration;
while(iterator.hasNext())
{
//加载并初始化实现类
configuration
=
iterator.next();
}
//对最后一个configuration类调用configure方法
configuration.configure(configFile);最后在调整LoggerFactory中初始化配置的方式为现在的SPI方式:package
com.github.kongwu.spisamples;
public
class
LoggerFactory
{
static
{
ServiceLoader
serviceLoader
=
ServiceLoader.load(SuperLoggerConfiguration.class);
Iterator
iterator
=
serviceLoader.iterator();
SuperLoggerConfiguration
configuration;
while(iterator.hasNext())
{
configuration
=
iterator.next();//加载并初始化实现类
}
configuration.configure(configFile);
}
public
static
getLogger(Class
clazz){
......
}
}等等,这里为什么是用iterator?而不是get之类的只获取一个实例的方法?试想一下,如果是一个固定的get方法,那么get到的是一个固定的实例,SPI还有什么意义呢?SPI的目的,就是增强扩展性。将固定的配置提取出来,通过SPI机制来配置。那既然如此,一般都会有一个默认的配置,然后通过SPI的文件配置不同的实现,这样就会存在一个接口多个实现的问题。要是找到多个实现的话,用哪个实现作为最后的实例呢?所以这里使用iterator来获取所有的实现类配置。刚才已经在我们这个
super-logger
包里增加了默认的SuperLoggerConfiguration实现。为了支持YAML配置,现在在使用方/用户的代码里,增加一个YAMLConfiguration的SPI配置:META-INF/services/com.github.kongwu.spisamples.SuperLoggerConfiguration:
com.github.kongwu.spisamples.ext.YAMLConfiguration此时通过iterator方法,就会获取到默认的XMLConfiguration和我们扩展的这个YAMLConfiguration两个配置实现类了。在上面那段加载的代码里,我们遍历iterator,遍历到最后,我们**使用最后一个实现配置作为最终的实例。再等等?最后一个?怎么算最后一个?使用方/用户自定义的的这个YAMLConfiguration一定是最后一个吗?这个真的不一定,取决于我们运行时的ClassPath配置,在前面加载的jar自然在前,最后的jar里的自然当然也在后面。所以如果用户的包在ClassPath中的顺序比super-logger的包更靠后,才会处于最后一个位置;如果用户的包位置在前,那么所谓的最后一个仍然是默认的XMLConfiguration。举个栗子,如果我们程序的启动脚本为:java
-cp
super-logger.jar:a.jar:b.jar:main.jar
example.Main默认的XMLConfigurationSPI配置在super-logger.jar,扩展的YAMLConfigurationSPI配置文件在main.jar,那么iterator获取的最后一个元素一定为YAMLConfiguration。搜索公众号后端架构师后台回复“面试”,获取一份惊喜礼包。但这个classpath顺序如果反了呢?main.jar在前,super-logger.jar在后java
-cp
main.jar:super-logger.jar:a.jar:b.jar
example.Main这样一来,iterator获取的最后一个元素又变成了默认的XMLConfiguration,我们使用JDKSPI没啥意义了,获取的又是第一个,还是默认的XMLConfiguration。由于这个加载顺序(classpath)是由用户指定的,所以无论我们加载第一个还是最后一个,都有可能会导致加载不到用户自定义的那个配置。所以这也是JDKSPI机制的一个劣势,无法确认具体加载哪一个实现,也无法加载某个指定的实现,仅靠ClassPath的顺序是一个非常不严谨的方式DubboSPIDubbo就是通过SPI机制加载所有的组件。不过,Dubbo并未使用Java原生的SPI机制,而是对其进行了增强,使其能够更好的满足需求。在Dubbo中,SPI是一个非常重要的模块。基于SPI,我们可以很容易的对Dubbo进行拓展。如果大家想要学习Dubbo的源码,SPI机制务必弄懂。接下来,我们先来了解一下JavaSPI与DubboSPI的用法,然后再来分析DubboSPI的源码。Dubbo中实现了一套新的SPI机制,功能更强大,也更复杂一些。相关逻辑被封装在了ExtensionLoader类中,通过ExtensionLoader,我们可以加载指定的实现类。DubboSPI所需的配置文件需放置在META-INF/dubbo路径下,配置内容如下(以下demo来自dubbo官方文档)。optimusPrime
=
org.apache.spi.OptimusPrime
bumblebee
=
org.apache.spi.Bumblebee与JavaSPI实现类配置不同,DubboSPI是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外在使用时还需要在接口上标注@SPI注解。下面来演示DubboSPI的用法:@SPI
public
interface
Robot
{
void
sayHello();
}
public
class
OptimusPrime
implements
Robot
{
@Override
public
void
sayHello()
{
System.out.println("Hello,
I
am
Optimus
Prime.");
}
}
public
class
Bumblebee
implements
Robot
{
@Override
public
void
sayHello()
{
System.out.println("Hello,
I
am
Bumblebee.");
}
}
public
class
DubboSPITest
{
@Test
public
void
sayHello()
throws
Exception
{
ExtensionLoader
extensionLoader
=
ExtensionLoader.getExtensionLoader(Robot.class);
Robot
optimusPrime
=
extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot
bumblebee
=
extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
}DubboSPI和JDKSPI最大的区别就在于支持“别名”,可以通过某个扩展点的别名来获取固定的扩展点。就像上面的例子中,我可以获取Robot多个SPI实现中别名为“optimusPrime”的实现,也可以获取别名为“bumblebee”的实现,这个功能非常有用!通过@SPI注解的value属性,还可以默认一个“别名”的实现。比如在Dubbo中,默认的是Dubbo私有协议:dubboprotocol-dubbo://**来看看Dubbo中协议的接口:@SPI("dubbo")
public
interface
Protocol
{
......
}在Protocol接口上,增加了一个@SPI注解,而注解的value值为Dubbo,通过SPI获取实现时就会获取
ProtocolSPI配置中别名为dubbo的那个实现,com.alibaba.dubbo.rpc.Protocol文件如下:filter=tocol.ProtocolFilterWrapper
listener=tocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
dubbo=tocol.dubbo.DubboProtocol
injvm=tocol.injvm.InjvmProtocol
rmi=tocol.rmi.RmiProtocol
hessian=tocol.hessian.HessianProtocol
tocol.http.HttpProtocol
tocol.webservice.WebServiceProtocol
thrift=tocol.thrift.ThriftProtocol
memcached=tocol.memcached.MemcachedProtocol
redis=tocol.redis.RedisProtocol
rest=tocol.rest.RestProtocol
registry=egration.RegistryProtocol
qos=tocol.QosProtocolWrapper然后只需要通过getDefaultExtension,就可以获取到@SPI注解上value对应的那个扩展实现了Protocol
protocol
=
ExtensionLoader.getExtensionLoader(Protocol.class).getDefaultExtension();
//protocol:
DubboProtocol还有一个Adaptive的机制,虽然非常灵活,但……用法并不是很“优雅”,这里就不介绍了Dubbo的SPI中还有一个“加载优先级”,优先加载内置(internal)的,然后加载外部的(external),按优先级顺序加载,如果遇到重复就跳过不会加载了。所以如果想靠classpath加载顺序去覆盖内置的扩展,也是个不太理智的做法,原因同上-加载顺序不严谨SpringSPISpring的SPI配置文件是一个固定的文件-
META-INF/spring.factories,功能上和JDK的类似,每个接口可以有多个扩展实现,使用起来非常简单://获取所有factories文件中配置的LoggingSystemFactory
List>
factories
=
SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class,
classLoader);下面是一段SpringBoot中spring.factories的配置#LoggingSystems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory
#PropertySourceLoaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
#ConfigDataLocationResolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\
org.springframework.boot.context.config.StandardConfigDataLocationResolver
......SpringSPI中,将所有的配置放到一个固定的文件中,省去了配置一大堆文件的麻烦。至于多个接口的扩展配置,是用一个文件好,还是每个单独一个文件好这个,这个问题就见仁见智了(个人喜欢Spring这种,干净利落)。搜索公众号后端架构师后台回复“架构整洁”,获取一份惊喜礼包。Spring的SPI虽然属于spring-framework(core),但是目前主要用在springboot中……和前面两种SPI机制一样,Spring也是支持ClassPath中存在多个spring.factories文件的,加载时会按照classpath的顺序依次加载这些spring.factories文件,添加到一个ArrayList中。由于没有别名,所以也没有去重的概念,有多少就添加多少。但由于Spri
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024苹果加工副产品深加工技术研发合同3篇
- 2025年度彩色激光打印机租赁及升级服务合同模板3篇
- 雪人的创意课程设计
- 年度雕刻雕铣设备控制系统竞争策略分析报告
- 2025年猕猴桃种植技术培训土地租赁与农民增收合同4篇
- 2025年度个人二手房交易合同模板环保装修服务版3篇
- 2025年离婚风险防范:协议离婚与诉讼离婚适用条件合同3篇
- 二零二五年度苗木出口业务代理销售合同4篇
- 二零二五版智能门窗控制系统集成与安装服务合同4篇
- 2025年度智能车位共享平台车位转让出租服务协议范本4篇
- 常见老年慢性病防治与护理课件整理
- 履约情况证明(共6篇)
- 云南省迪庆藏族自治州各县区乡镇行政村村庄村名居民村民委员会明细
- 设备机房出入登记表
- 六年级语文-文言文阅读训练题50篇-含答案
- 医用冰箱温度登记表
- 零售学(第二版)第01章零售导论
- 大学植物生理学经典05植物光合作用
- 口袋妖怪白金光图文攻略2周目
- 光伏发电站集中监控系统通信及数据标准
- 三年级下册生字组词(带拼音)
评论
0/150
提交评论