⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 飞天小牛肉 「小牛肉」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

什么是 SPI

SPI 全称为 (Service Provider Interface) ,是 JDK 内置的一种服务提供发现机制,为使用者提供了服务扩展点,使用者无需修改源码就可以自定义服务实现,简单理解就是为服务的具体实现提供了一个可插拔机制。

比如你的项目中

SPI 的Java doc 如下,感兴趣的小伙伴可以阅读下:

A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service. The classes in a provider typically implement the interfaces and subclass the classes defined in the service itself. Service providers can be installed in an implementation of the Java platform in the form of extensions, that is, jar files placed into any of the usual extension directories. Providers can also be made available by adding them to the application’s class path or by some other platform-specific means

Java SPI 实战

设想这样一个场景,你写了一个框架要给别人用,你内置了一些序列化的方法,但是总有些使用者会有自己的特殊的序列化逻辑,这时候你就可以把序列化方法的接口通过 SPI 暴露出去,交给使用者来自定义实现。这样,使用者就可以接入自己自定义的序列化方法。

如下,我们定义一个序列化接口:

重点在这里的 load 方法,我们通过 ServiceLoader.load() 来加载指定的 Serializer 实现类列表,然后使用第一个实现类。具体底层原理咱们先按下不表。

再随便写两个实现类实现这个接口:

再建立一个以暴露出去的接口的全路径为命名的文件,比如我这里的就是 com.example.draft.Serializer,注意这个文件一定要放在 resources/META-INF/services 下面

文件的内容也很简单,就是你想使用的 Serializer 接口具体实现类的全路径,多个实现类之间直接换行就可以,比如:

至此,SPI 机制全部完成,我们来测试下:

我们通过 load 方法加载了配置文件中配置的第一个实现类(也就是 SerializerImpl2),并调用了它的 serialize 方法,输出的内容是:

以上,是单体项目的示范,你可以把这个项目打成 jar 包,别人导入这个 jar 包的时候,同样通过上述配置文件的方法,来自定义暴露出来的 SPI 接口。

Java SPI 原理

不难理解,SPI 的重点就是 ServiceLoader,看源码,我们发现这个类是一个 final 类型的,所以是不可被继承修改,同时它实现了 Iterable 接口,方便后续我们能够通过迭代的方式得到对应的服务实现。

再往下,可以看到一个熟悉的常量定义 META-INF/services/:

重点看 load 方法:

简单来说,load 的核心逻辑就是使用指定的类型和当前线程绑定的类加载器(ClassLoader)实例化了一个 LazyIterator 对象,并清除 Provider 缓存。

ServiceLoader 实现了 Iterable 接口的方法后,具有了迭代的能力,在 iterator() 方法被调用时,首先会在 ServiceLoader 的 Provider 缓存中进行查找,如果缓存中没有命中那么则在 LazyIterator 中进行查找:

在调用 LazyIterator 时,具体实现如下:

没有什么太复杂的逻辑,我这里只截取了关键代码,核心逻辑就 3 点:

  1. 到预先定义的地方(META-INF/service/+类名)去找配置文件
  2. 解析配置文件拿到全类名
  3. 通过反射获取实现类
文章目录
  1. 1. 什么是 SPI
  2. 2. Java SPI 实战
  3. 3. Java SPI 原理