1.背景
在开题之前大家有没有这个疑惑,就是随着业务的不管发展和壮大,我们的工程结构以及代码量越来越大,交织在一起,你中有我,我
中有你,导致项目结构分工不是很明确,职责不清晰,如果线上有紧急问题需要排查的时候,我们可能就手忙脚忙了,有时候也不知道问
题出现在那个模块,导致问题排查效率低下,同时不利于项目复盘和明确职责,又或者老板想看下业务成功率,服务成功率等等一些数据的时
候,我们可能为了方便直接在代码里面写了,随着业务的不管迭代,新接手的同事可能就会绞尽脑汁,这段代码到底是干什么的,为什么
要写在这里(也许这段代码根本没有任何的实际业务含义,就是一个埋点或者数据统计方面的代码)等等这些问题,今天我们隆重的给大家介
绍一种新的解决方案来解决这些问题,这个方案就是SPI,全称是Service Provider Interface,当然大家可能说也有别的方案,是,确实是
有别的方案,条条道路通罗马,OK,我们废话不多说,进入正题。
2.什么是SPI
SPI(Service Provider Interface),是JDK提供的一套用来被第三方实现或者扩展的API,它是一种JVM层面的服务注册发现机制,
可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用。SPI机制主要思想是将装配的控制权移到程序之外,在组件化
设计中这个机制尤其重要,其核心思想就是解耦。
2.1: SPI整体机制
如下 图1所示:
图1
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,最核心的思想就是服务注册+服务发现
2.2: SPI和API区别
那么讲到这里,很多人可能就有疑问,说这个和API调用有什么区别呢,OK,为了更清楚的把这个问题讲明白,我们使用具体的图来说明
SPI与API区别:
API
图2所示
图2
SPI
图3所示
图3
一般模块之间通信基本上都是通过接口,那我们在服务调用方和服务实现方(也称服务提供者)之间引入一个“接口概念”。
当实现方提供了接口和实现,我们可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是 API ,这种接口和实现都是放在实现方的。
接口和实现方属于同一个模块,密切不可分割。
当接口存在于调用方这边时,就是 SPI ,由接口调用方确定接口规则,然后由不同的具体业务去根据这个规则对这个接口进行实现,从而提供服务,
举个通俗易懂的例子:一个电脑制造公司,设计好了充电器标准图纸以后,那么接下来就可以把这个图纸分发给不同的厂商去生产,最后只有严格按
照图纸要求,就可以生产合格的商品。
通过上面的图2和图3以及配合上面的文字介绍,相信大家应该很非常清楚API和SPI的区别了.
3.SPI作用
SPI的发现能力是不需要依赖于其他类库,主要实现方式是:
java.util.ServiceLoader#load JDK自身提供的加载能力
最重要的作用就是:解耦
4.实现原理
4.1: 源码分析
public final class ServiceLoader<S> implements Iterable<S> { //配置文件所在的包目录路径 private static final String PREFIX = "META-INF/services/";
// 接口名称 private final Class<S> service;
// 类加载器 private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created // Android-changed: do not use legacy security code. // private final AccessControlContext acc;
//providers就是不同实现类的缓存,key就是实现类的全限定名,value就是实现类的实例 private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// //内部类LazyIterator的实例 private LazyIterator lookupIterator;
public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); }
private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; // Android-changed: Do not use legacy security code. // On Android, System.getSecurityManager() is always null. // acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); }
private static void fail(Class<?> service, String msg, Throwable cause) throws ServiceConfigurationError { throw new ServiceConfigurationError(service.getName() + ": " + msg, cause); } |