SPI技术理解及应用

一、什么是SPI ?

SPI 全称:Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。

面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。

为了实现在模块装配的时候不用在程序里动态指明,这就需要一种服务发现机制。java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。这有点类似IOC的思想,将装配的控制权移到了程序之外。

SPI的作用就是为被扩展的API寻找服务实现。

SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦。

本质:Java SPI 实际上是“基于接口的编程+策略模式+约定配置文件” 组合实现的动态加载机制,在JDK中提供了工具类:“java.util.ServiceLoader”来实现服务查找。

SPI整体机制图如下:

当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。

二、SPI 的不足

1.不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。

2.获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。(Spring 的BeanFactory,ApplicationContext 就要高级一些了。)

3.多个并发多线程使用 ServiceLoader 类的实例是不安全的。

三、API 与 SPI

API代表应用程序编程接口,其中API是一种访问某种软件或平台提供的服务/功能的方法。
SPI代表服务提供商接口,其中SPI是注入,扩展或更改软件或平台行为的方式。

API通常是用作客户端访问服务,并且具有以下属性:
1、API是一种访问服务以实现特定行为或输出的编程方式。
2、但是API一旦被客户端使用,除非有适当的通信,否则它不能(也不应)被更改/删除。
SPI面向服务提供者,并且具有以下属性:
1、SPI是一种扩展/更改软件或平台行为的方式。
2、添加SPI接口将导致问题并可能破坏现有的实现。
换句话说,API会告诉您特定的类/方法为您执行什么操作,而SPI则告诉您必须执行哪些操作才能符合要求。

API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。

SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。

四、SPI 应用场景

SPI扩展机制应用场景有很多,比如Common-Logging,JDBC,Dubbo等等。

SPI流程:

  • 有关组织和公式定义接口标准
  • 第三方提供具体实现: 实现具体方法, 配置 META-INF/services/${interface_name} 文件
  • 开发者使用

比如JDBC场景下:

首先在Java中定义了接口java.sql.Driver,并没有具体的实现,具体的实现都是由不同厂商提供。
在MySQL的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。
同样在PostgreSQL的jar包PostgreSQL-42.0.0.jar中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是PostgreSQL对Java的java.sql.Driver的实现。

个人简单描述下使用的过程(源码就不贴了):

  1. 建立链接程序提前执行ServiceLoader对java.sql.Driver的具体实现进行实例化。
  2. 具体的实例化中会获取当前服务的一些配置属性,封装完善该Driver具体实现类。
  3. 建立链接的方法使用到具体的Driver实现类建立链接。
五、ServiceLoader原理

服务端很好理解,就是一个定义,客户端来看看ServiceLoader的源码。

首先,ServiceLoader实现了Iterable接口,所以它有迭代器的属性,这里主要都是实现了迭代器的hasNext和next方法。这里主要都是调用的lookupIterator的相应hasNext和next方法,lookupIterator是懒加载迭代器。

其次,LazyIterator中的hasNext方法,静态变量PREFIX就是”META-INF/services/”目录,这也就是为什么需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件。

最后,通过反射方法Class.forName()加载类对象,并用newInstance方法将类实例化,并把实例化后的类缓存到providers对象中,(LinkedHashMap<String,S>类型) 然后返回实例对象。

六、代码实践案例

1、利用三方jar包来利用注解生成META-INFO/services, 注解:@MetaInfServices

<dependency>
    <groupId>org.kohsuke.metainf-services</groupId>
    <artifactId>metainf-services</artifactId>
</dependency>

定义客户端为interface工程,定义学习接口Study,定义方法为学习方式

定义服务端是provider工程,定义三个实现类为配置组件,加入注解 @MetaInfServices

编译之后target存在配置文件

2、直接使用框架的SPI机制(Spring SPI)
Spring SPI介绍:

  • 工具类:Spring中使用的类是SpringFactoriesLoader,在org.springframework.core.io.support包中
  • SpringFactoriesLoader 会扫描 classpath 中的 META-INF/spring.factories文件。
  • SpringFactoriesLoader 会加载并实例化 META-INF/spring.factories 中的指定类型。
  • META-INF/spring.factories 内容必须是 properties 的Key-Value形式,多值以逗号隔开。

使用细节:

  • spring.factories内容的key不只能是接口,也可以是抽象类、具体的类。但是有个原则:=后面必须是key的实现类(子类)
  • key还可以是注解,比如SpringBoot中的的key:org.springframework.boot.autoconfigure.EnableAutoConfiguration,它就是一个注解。
  • 文件的格式需要保证正确,否则会返回[](不会报错)
  • =右边必须不是抽象类,必须能够实例化。且有空的构造函数~
  • loadFactories依赖方法loadFactoryNames。loadFactoryNames方法只拿全类名,loadFactories拿到全类名后会立马实例化。
  • 此处特别注意:loadFactories实例化完成所有实例后,会调用AnnotationAwareOrderComparator.sort(result)排序,所以它是支持Ordered接口排序的,这个特点特别的重要。

更多精彩文章请访问我的个人博客(zhuoerhuobi.cn)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值