JDK SPI 和 Dubbo SPI

SPI (Service Provider Interface),简单翻译就是服务提供接口,这里的“服务”泛指任何一个可以提供服务的功能、模块、应用或系统,会预留一些口子或者扩展点,只要按照既定的规范去开发,就可以动态加载一些不同的实现类,以实现一些自定义的扩展。

JDK自带SPI实现机制,但是也有很多对JDK的SPI做增强的实现,如Dubbo中的SPI,下边就对比JDK的SPI和Dubbo的SPI。

JDK SPI

JDK是通过ServiceLoader实现了SPI,具体来看ServiceLoader的核心代码逻辑

image

核心流程分为三块:

  • 第一块,将接口传入到ServiceLoader.load方法后,得到了一个内部类的迭代器。
  • 第二块,通过调用迭代器的hasNext方法,读取/META-INF/services/接口类路径这个资源文件内容,并逐行解析出所有实现类的类路径。
  • 第三块,将所有实现类的类路径通过Class.forName反射方式进行实例化对象。

这个地方,每调用一次ServiceLoader的load方法,就会创建新的实例对象,当调用load方法的次数很少时,创建新的实例化对象影响并不大,也就占用了一点内存,可以忽略不记。当调用load方法的频率比较高,那么每次调用都要读取文件、解析文件、反射实例化这几步,就会影响磁盘IO读取、内存开销也会明显增加。

每次调用load方法的时候,想要拿到其中一个实现类,因为不知道想要的实现类在迭代器中的位置,需要遍历所有的实现类,项目中,很多需要获得指定的实现类,会产生很多雷同的代码。

JSK SPI的问题

JDK的SPI创建出多个实例化对象,有两个问题:

  • 问题1,使用load方法频率高,容易影响IO吞吐和内存消耗。
  • 问题2,使用load方法想要获取指定实现类,需要自己进行遍历并编写比较代码。

针对问题1,我们可以尝试增加缓存,在第一次调用load方法时,通过读取文件、解析文件、反射实例化拿到接口的所有实现类并缓存,后续的调用直接从缓存中读取,减少资源消耗,提升性能。

针对问题2,问题就是解决遍历查找的时间复杂度是O(n),将时间复杂度从O(n)降低到O(1),采用空间换时间的方式,可以使用Map,Map 支持通过不同的一个对象获取另外一个对象,类似通过别名获取另外一个对象,比较符合常规查找操作。

Dubbo SPI

为了弥补我们分析的 JDK SPI 的不足,Dubbo 也定义出了自己的一套 SPI 机制逻辑,既要通过 O(1) 的时间复杂度来获取指定的实例对象,还要控制缓存创建出来的对象,做到按需加载获取指定实现类,并不会像 JDK SPI 那样一次性实例化所有实现类。

JDK SPI 的使用三部曲:

  • 首先,定义一个接口。
  • 然后,定义一些类来实现该接口,并将多个实现类的类路径添加到“/META-INF/services/ 接口类路径”文件中。
  • 最后,使用 ServiceLoader 的 load 方法加载接口的所有实现类。

学习来源:极客时间 《Dubbo源码剖析与实战》 学习笔记 Day12

猜你喜欢

转载自blog.csdn.net/qq_38862257/article/details/128772261