某厂面试:如何优雅使用 SPI 机制( 三 )


这段代码整体逻辑不算复杂 , 所以也有点自信回头 , 就没跑单元测试 , 不过问题应该不大 。解释一下其中具体逻辑:

  1. FileServiceFactory 大家可以理解为文件服务对外的统一访问入口 。实现了 spirng 初始化的一个接口 , 可以在 bean 初始化时进行代码逻辑操作
  2. bean 初始化时 , 通过 ServiceLoader 类加载器负责加载对象存储接口 , 这样就能加载到客户端存放到 META-INF/services 中的自定义对象存储实现
  3. 获取到自定义对象存储后 , 和服务端本身自带的对象存储一起存放至容器中 , 这样就可以根据项目中的 fileStoreType 获取对应的服务了
结合实际的项目场景 , 一个简简单单的 SPI 应用就完成了 , 自我感觉比 JDBC 装配的例子更好理解一些
上面的业务只是为了让不理解 SPI 的小伙伴更好的掌握应用场景 , 其实对象存储服务是一种可穷举的业务场景 , SPI 并不是唯一的解决思路 。当然 , 为了省事使用 SPI 也没啥问题 。最后提一句 , SPI 最合适的还是没有统一业务实现场景 , 就像上面提到过的加密算法
深入解析 SPI一篇技术解析文章 , 适当放一些源码解析感觉会更好一些 。下面一起来看看 ServiceLoader底层都做了什么事情
通过 ServiceLoader 的 load 方法创建一个新的 ServiceLoader , 并实例化其中的成员变量
某厂面试:如何优雅使用 SPI 机制

文章插图
 
应用程序通过迭代器接口获取对象实例 , 这里首先会判断 providers 对象中是否有实例对象
如果有实例 , 那么就返回;如果没有 , 执行类的装载步骤 , 具体类装载实现如下:
  1. LazyIterator#hasNextService 读取 META-INF/services 下的配置文件 , 获得所有能被实例化的类的名称 , 并完成 SPI 配置文件的解析
  2. LazyIterator#nextService 负责实例化 hasNextService() 读到的实现类 , 并将实例化后的对象存放到 providers 集合中缓存

某厂面试:如何优雅使用 SPI 机制

文章插图
 
如果你不知道上面的一些 "黑话" 不要紧 , 因为都是 ServiceLoader 底层执行的方法 , 跟着下面这个程序敲一遍代码就懂了
某厂面试:如何优雅使用 SPI 机制

文章插图
 
这里为了跟源码 , 也是把上面对象存储的逻辑 , 简单写了个 SPI 示例 , 证明是没有问题的 。如果小伙伴想真正了解 , 就需要跟下源码去看看 , 其它源码部分就不细说了
结言上面说了很多关于 SPI 机制的优点以及应用场景 , 这里总结下关键内容
  1. SPI 机制优势就是解耦 。将接口的定义以及具体业务实现分离 , 而不是和业务端全部耦合在一端 。可以实现 运行时根据业务实际场景启用或者替换具体组件
  2. SPI 机制的场景就是 没有统一实现标准的业务场景 。一般就是 , 服务端有标准的接口 , 但是没有统一的实现 , 需要业务方提供其具体实现 。比如说 JDBC 的 JAVA.sql.Driver 接口和不同云厂商提供的数据库实现包
每个事物都是既有优点 , 同时也伴随着缺点 。要从两个方面去看 , 不能总盯着一方面 。这里说一下 SPI 机制的缺点
  1. 不能按需加载 。虽然 ServiceLoader 做了延迟加载 , 但是只能通过遍历的方式全部获取 。如果其中某些实现类很耗时 , 而且你也不需要加载它 , 那么就形成了资源浪费
  2. 获取某个实现类的方式不够灵活 , 只能通过迭代器的形式获取 。这两点可以参考 Dubbo SPI 实现方式进行业务优化
文章通过图文并茂的方式帮助大家重新梳理了一遍 SPI 的场景、优势和缺点 , 看完文章后相信大家对 SPI 机制有了更深入的认识
梳理出 SPI 的场景以及优势后 , 小伙伴最好再去 Debug 源代码 , 这样会大家对 SPI 的实现才能更加清楚 。只有对一个知识点真正掌握 , 才不至于事后很快遗忘


推荐阅读