前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java SPI (Service Provider Interface) 机制详解

Java SPI (Service Provider Interface) 机制详解

作者头像
一个会写诗的程序员
发布2021-02-04 11:08:15
8K0
发布2021-02-04 11:08:15
举报

概述

关键词:解耦,可拔插,面向接口编程,动态类加载。

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

什么是SPI ?

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

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

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

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

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

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

SPI与API区别:

API是调用并用于实现目标的类、接口、方法等的描述;

SPI是扩展和实现以实现目标的类、接口、方法等的描述;

换句话说,API 为操作提供特定的类、方法,SPI 通过操作来符合特定的类、方法。

参考: https://stackoverflow.com/questions/2954372/difference-between-spi-and-api?answertab=votes#tab-top

SPI和API的使用场景解析:

  • 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的实现。

项目案例

Java 工程目录:

下面我们来简单实现一个 JDK 的SPI的简单实现。

Java代码开发

首先第一步,定义一个接口:

Phone.java

这个接口分别有两个实现:

Huawei.java
IPhone.java

约定配置:新建 META-INF/services 目录

注意:这个META-INF/services 目录是写死的约定,在 java.util.ServiceLoader 源码实现中, java.util.ServiceLoader#PREFIX 可以看到这个目录的硬编码。

然后需要在resources目录下新建 META-INF/services 目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件:

com.light.sword.Phone (这是一个文件,是的,一切皆是文件。)

在这个文件中写入接口的实现类的全限定名(文件 com.light.sword.Phone 中写死的内容):

如下图所示:

加载实现类并调用服务

这时,通过ServiceLoader 加载实现类并调用服务:

Main.java

输出如下:

工程源代码:https://gitee.com/universsky/java-spi-demo

这样一个简单的 Java SPI 的demo就完成了。可以看到其中最为核心的就是通过一系列的约定(其实,就是按照人家 java.util.ServiceLoader 的规范标准来), 然后,通过ServiceLoader 这个类来加载具体的实现类,进而调用实现类的服务。


知识拓展: 其实,我们在Spring框架中,可以通过 component-scan 标签来对指定包路径进行扫描,只要扫到 Spring 制定的 @Service@Controller 等注解,spring自动会把它注入容器。 这就相当于spring制定了注解规范,我们按照这个注解规范开发相应的实现类或controller,spring并不需要感知我们是怎么实现的,他只需要根据注解规范和scan标签注入相应的bean,这正是 spi 理念的体现。

SPI 实现原理解析

首先,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>类型) 然后返回实例对象。


java.util.ServiceLoader.java 源代码如下:

参考资料

https://www.cnblogs.com/jy107600/p/11464985.html http://blog.itpub.net/69912579/viewspace-2656555/ https://segmentfault.com/a/1190000020422160

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 什么是SPI ?
    • SPI 的不足
      • SPI与API区别:
      • SPI和API的使用场景解析:
      • Java代码开发
      • 约定配置:新建 META-INF/services 目录
      • 加载实现类并调用服务
  • API 与 SPI
  • SPI 应用场景
  • 项目案例
  • SPI 实现原理解析
  • 参考资料
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档