一、SPI机制是什么?

        spi全称为 (Service Provider Interface),是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制,一种解耦非常优秀的思想。

        spi的工作原理: 就是ClassPath路径下的META-INF/services文件夹中, 以接口的全限定名来命名文件名,文件里面写该接口的实现。然后再资源加载的方式,读取文件的内容(接口实现的全限定名), 然后再去加载类。

spi可以很灵活的让接口和实现分离, 让api提供者只提供接口, 第三方来实现。

 优点:

  • 使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点:

  • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
  • 多个并发多线程使用ServiceLoader类的实例是不安全的。

二、SPI如何使用

        那么现在有这样的场景:当我的项目里面有什么支付模块我就使用什么样的支付模块,比如说有支付宝支付模块就选择支付宝、有微信支付模块我就选择微信支付、同时有多个的时候,我默认选择第一个,此时我们就可以使用SPI,先看下如何使用。

1、创建META-INF/services文件夹,然后创建一个以Pay接口全限定名为名字的文件

2、在文件中编写想要实现哪个Pay的实现类(AliPay,WechatPay,BankCardPay),注意也要是全限定名。假如是是支付宝支付的模块,上面文件的内容:com.taolong.dubbo.spi.strategy.AliPay

 3、获取Pay并调用

获取并调用的逻辑,我就修改下上面的策略模式中的Context的invokerStrategy方法,这里假设默认使用第一个

public void invokeStrategy(){
    ServiceLoader<Pay> payServiceLoader = ServiceLoader.load(Pay.class);
    Iterator<Pay> iterator = payServiceLoader.iterator();
    if (iterator.hasNext()){
        iterator.next().pay();
    }
}

三、SPI的优秀实现案例

        我们来看一个真实的使用上述SPI的例子—数据库驱动(Driver)。

        我们知道,当我们的项目里面使用引用了mysql的驱动pom依赖时,我们的项目里面会自动选择使用mysql的驱动,我们甚至不需要手动去加载。我们来看看它的具体实现。

1、首先看一下java.sql.Driver的类,这里面也是相当于定义了一个规范

2、其次看mysql驱动包的META-INF/services文件夹下面有没有指定的文件

很熟悉,命名就是java.sql.Driver

3、打开文件查看一下文件内容

 

        这里面就能看到我们的mysql的驱动了,到这里基本上就确认这也是使用SPI实现的,顺便说一下,现在为什么我们不需要使用Class.forName()去加载驱动了,这是因为DriverManager使用SPI的机制已经帮我们加载好了,我们来看看DriverManager的类

 不管是文件名还是文件内容都是全限定名,所以通过反射很容易创建相应的类

在现有框架中的使用

        其实了解SPI机制是因为最近看SpringBoot代码的时候发现的,我们知道在SprngBoot中好多的配置和实现都有默认的实现,我们只需要修改部分配置,比如数据库配置,我们只要在配置文件中写上对应的url,username,password就可以使用了。其实他这边用的就是SPI的方式实现的

不过Spring使用的只是和JDK中的原理相同而已。

JDK使用的工具类是ServiceLoader

Spring中使用的类是SpringFactoriesLoader,在 org.springframework.core.io.support包中

区别:

文件路径不同 spring配置放在 META-INF/spring.factories中

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐