SPI是什么?一起来看看


SPI是什么?一起来看看

文章插图
 
引语
平时API倒是听得很多?SPI又是啥.别急我们来先看看面向接口编程的调用关系,来了解一下,API和SPI的相似和不同之处 。
SPI理解
先来一段官话的介绍:SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.(听了一脸懵逼)好的,我们结合图片来理解一下 。
SPI是什么?一起来看看

文章插图
 
简单的来说分为调用方,接口,服务方.接口就是协议,契约,可以调用方定义,也可以由服务方定义,也就是接口是可以位于调用方的包或者服务方的包. 1.接口的定义和实现都在服务方的时候,仅暴露出接口给调用方使用的时候,我们称为API; 2.接口的定义在调用方的时候(实现在服务方),我们给它也取个名字--SPI 。应该还比较好理解吧?
SPI的使用场景
SPI在框架中其实有很多广泛的应用,这里列举几个例子: 1.MySQL驱动的选择driverManager根据配置来确定要使用的驱动;
2.dubbo框架中的扩展机制
使用实例
看完上面的简介和SPI在框架中的应用,想必对SPI在读者的大脑中已经产生了一个雏形,talk is cheap!show me the code.说了这么多,我们具体写一个简单的例子来看看效果,验证一下SPI.
1.首先定义一个接口,忍者服务接口
public interface NinjaService { void performTask();}2.接下来写两个实现类,ForbearanceServiceImpl(上忍),ShinobuServiceImpl(下忍)
public class ForbearanceServiceImpl implements NinjaService { @Override public void performTask() { System.out.println("上忍在执行A级任务"); }}public class ShinobuServiceImpl implements NinjaService { @Override public void performTask() { System.out.println("下忍在执行D级任务"); }}3.接下来我们在main/resources/下创建META-INF/services目录,并且在services目录下创建一个com.scott.JAVA.task.spi.NinjaService(忍者服务类的全限定名)的文件.
4.创建一个Client场景类来调用看看结果
SPI是什么?一起来看看

文章插图
 
很完美的调用了两个实现类的performTask()方法.
5.最后贴一下目录结构
SPI是什么?一起来看看

文章插图
 
【SPI是什么?一起来看看】SPI源码简单分析
1.先看下核心类ServiceLoader的定义和属性
// 继承了Iterable类 遍历的时候使用public final class ServiceLoader<S> implements Iterable<S>{ // 这就是为啥需要在META-INF/services/目录下创建服务类的文件 private static final String PREFIX = "META-INF/services/"; // 被加载的服务 private final Class<S> service; // 类加载器 private final ClassLoader loader; // 访问控制类 private final AccessControlContext acc; // 实现类的缓存 根据初始化的顺序 也就是在/services/文件中的定义顺序来定义的加载顺序 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 懒加载iterator private LazyIterator lookupIterator;2.然后从client开始,然后依次debug进去
ServiceLoader<NinjaService> ninjaServices = ServiceLoader.load(NinjaService.class); public static <S> ServiceLoader<S> load(Class<S> service) { // 获取当前的类加载器 也就是AppClassLoader ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }后面的就省略了,因为这里仅仅就是根据NinjaService初始化的事项,没有什么很难理解的点.
3.我们在看看具体的调用过程,这里使用的是client对应的class文件,因为增加for(foreach)在java中是个语法糖,实际上编译后是这样的内容
public static void main(String[] args) { ServiceLoader<NinjaService> ninjaServices = ServiceLoader.load(NinjaService.class); // 这里一下其实就是增加for解糖后的代码 有兴趣可以去了解下java的语法糖 Iterator var2 = ninjaServices.iterator(); while(var2.hasNext()) { NinjaService item = (NinjaService)var2.next(); item.performTask(); } }4.随着断点继续走,我们进入到var2.hasNext()的方法
public boolean hasNext() { // knownProviders还没有加载过provider 走下面的分支 if (knownProviders.hasNext()) return true; return lookupIterator.hasNext(); }这里lookupIterator上面ServiceLoader的属性介绍过,它其实是ServiceLoader中的一个Iterator的内部类 。然后调用了内部类Iterator的hasNext()方法 。


推荐阅读