java spi 缺点及改进_JAVA的SPI机制-介绍与感受

JAVA的SPI机制-介绍与感受

简单介绍

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。

在许多第三方框架中,SPI机制都得以运用。比如JDBC,Slf4j,Dubbo,spring等。

在使用后jdbc的时候,我们都是通过DriverManager.getConnection获取数据库的连接,连接MySQL时,引入mysql的驱动;连接sqlserver时,引入sqlserver的驱动。。。获取连接的代码始终没变,这就用到了SPI的机制,更多原理参考。这样就使得驱动更像是一个可插拔,可替换换的组件,需要那个,引入那个便可,JDBC只是提供了一个java连接数据库的规范,每个厂商只需要实现规范提供对应的驱动,然后通过SPI机制加载驱动进行使用。

Slf4j也是一样,提供了一套输出日志的规范,具体实现可以有logback,log4j,java-logging,slf4j-nop,slf4j-simple等等。当时用的时候,只需要引入一个对应的实现即可。

SPI机制

JAVA SPI的具体约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口类全路径的文件。该文件里的内容就是实现该服务接口的具体实现类的全路径。当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。

基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader,通过load方法就可以对META-INF/services里面的实现类进行加载和实例化。

写个demo感受一下

场景,现在有一个短信发送的需求,根据不同的业务场景,项目需要选择不同的运营商,要求:

在切换运营商的时候不要对代码进行改动

保证扩展性

使用方便

当然,这个需求的解决方案肯定不止一个,但是通过这个例子可以直观的感受SPI是个啥。

项目结构

20200718035422-5f12726ea157e.jpg

sms-api: 定义了一个短信发送提供者ISMSProvider应该具备的功能,发送短信

sms-provider-telecom: 电信,实现了ISMSProvider

sms-provider-unicom: 联通,实现了ISMSProvider

user: 模拟用户调用

sms-api

短信提供商接口定义

public interface ISMSProvider {

void sendSMS(String msg);

}

同时,提供了一个工厂类,获取短信提供商,方便用户调用

public class SMSProviderFactory {

private SMSProviderFactory() {

throw new IllegalStateException("Utinity Class");

}

public static ISMSProvider getProvider() {

ServiceLoader smsProviders = ServiceLoader.load(ISMSProvider.class);

Iterator smsIterator = smsProviders.iterator();

if (!smsIterator.hasNext()) {

throw new IllegalStateException("No valid SMS provider is found!");

}

ISMSProvider provider = smsIterator.next();

System.out.println("Actual SMS provider is: " + provider.getClass());

return provider;

}

}

为了方便,如果同时引入了多个提供商的情况下,默认用第一个。

sms-provider-telecom

对ISMSProvider进行实现

public class TelecomSMSProvider implements ISMSProvider {

public void sendSMS(String msg) {

System.out.println(String.format("Send SMS [%s] by Telecom...", msg));

}

}

最重要的是要在classpath下面准备Java SPI需要的文件,这里是META-INF/services/top.njlife.sms.ISMSProvider, 内容为

top.njlife.sms.TelecomSMSProvider

sms-provider-unicom

与上面一样,进行接口实现

public class UnicomSMSProvider implements ISMSProvider {

public void sendSMS(String msg) {

System.out.println(String.format("Send SMS [%s] by Unicom...", msg));

}

}

准备SPI需要的文件,文件内容为META-INF/services/top.njlife.sms.ISMSProvider。

注意: 文件名都是实现的接口的全路径名。

top.njlife.sms.UnicomSMSProvider

到此两个短信提供商就开发好了。

user

用户在使用的时候,只需要在pom里面引入

top.njlife

sms-api

1.0-SNAPSHOT

首先试试telecom,继续引入

top.njlife

sms-provider-telecom

1.0-SNAPSHOT

模拟调用代码

public class SMSSender {

public static void main(String[] args) {

ISMSProvider provider = SMSProviderFactory.getProvider();

provider.sendSMS("test msg");

}

}

运行,结果如下

Actual SMS provider is: class top.njlife.sms.TelecomSMSProvider

Send SMS [test msg] by Telecom...

此时我们需要切换到unicom,在pom里面telecom的依赖改成

top.njlife

sms-provider-unicom

1.0-SNAPSHOT

再次运行代码,得到

Actual SMS provider is: class top.njlife.sms.UnicomSMSProvider

Send SMS [test msg] by Unicom...

可以看到,短信提供商成功切换了,项目代码不需要做任何改动。

如果这时候需要新的集成新的短信提供商,只需要再开发一个项目,然后引入依赖即可。

这就是SPI的方便之处,基于这个机制,我们可以方便地做到在一个系统/框架中实现一个插件的功能,或者扩展点,可以参考Dubbo的SPI机制。

至于JAVA的SPI内部机制是如何做到的,后续继续探讨。

简单总结

SPI机制可以帮助我们轻松实现解耦,使得第三方服务提供者模块独立于业务代码之外,实现模块的插拔。

但是JAVA原生的SPI也有一些不足的地方

无法按需加载。ServiceLoader每次都会加载所有的实现,如果有的没有用到也进行加载和实例化,会造成一定系统资源的浪费。

线程安全问题。ServerLoader可以看做是一个工具类,提供了很多static方法,但是其内部用到了一些成员变量,这样就会导致在多线程调用的时候有线程安全问题,需要注意。

异常吞噬。ServerLoader在加载类的过程中如果出现异常无法加载没有相关的异常抛出,导致一旦出现问题需要花时间进行定位。

鉴于这些缺点,很多开源框架都实现了一套自己的SPI机制,比如Dubbo对SPI进行了增强,参考:https://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

Demo源码

最后附上文中demo的源代码:https://gitee.com/nickhan/java-spi

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值