Java SPI(Service Provider Interface),是JDK提供的一套用来被第三方实现或者扩展的接口,通过java.util.ServiceLoader类加载META-INF/services/中的配置进行服务发现,可以用来启用框架扩展和替换组件。主要好处在于解耦,可拔插,面向接口编程,本质是基于接口的编程+策略模式+约定配置文件组合实现的动态加载机制。

    这种思想被广泛的应用到各种框架及其实现中,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和Oracle都有不同的实现提供,而Java的SPI机制可以为某个接口寻找服务实现。再比如Spring中也有一种类似与Java SPI的扩展加载机制,在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。这种自定义的SPI机制是Spring Boot Starter实现的基础。

    这里要注意SPI与API区别:

    • API大多数情况下,都是实现方制定并、实现接口,调用方仅仅调用接口,且不能选择实现, 从使用人员上来说,API 一般被应用开发人员使用;

    SPI机制原理简析 - 图1

    • SPI 是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现, 从使用人员上来说,SPI 被框架扩展人员使用。

    SPI机制原理简析 - 图2
    换句话说,API 为操作提供特定的类、方法,SPI 通过操作来符合特定的类、方法。

    使用Java的SPI来模拟一个简单的实现
    定义一个数据源加载接口

    1. /**
    2. * 数据源驱动程序
    3. *
    4. * @author starsray
    5. * @since 2022-02-10
    6. */
    7. public interface DatasourceDriver {
    8. void loadDriver();
    9. }

    根据规范实现接口

    • MySQL实现

      1. /**
      2. * mysql driver
      3. *
      4. * @author starsray
      5. * @since 2022-02-10
      6. */
      7. public class MySQLDriver implements DatasourceDriver {
      8. @Override
      9. public void loadDriver() {
      10. System.out.println("loaded mysql driver");
      11. }
      12. }
    • Oracle实现

      1. /**
      2. * oracle driver
      3. *
      4. * @author starsray
      5. * @since 2022-02-10
      6. */
      7. public class OracleDriver implements DatasourceDriver {
      8. @Override
      9. public void loadDriver() {
      10. System.out.println("loaded oracle driver");
      11. }
      12. }

      测试代码

      1. /**
      2. * TestSPI
      3. *
      4. * @author starsray
      5. * @since 2022-02-10
      6. */
      7. public class TestSPI {
      8. public static void main(String[] args) {
      9. ServiceLoader<DatasourceDriver> drivers = ServiceLoader.load(DatasourceDriver.class);
      10. for (DatasourceDriver d : drivers) {
      11. d.loadDriver();
      12. }
      13. }
      14. }

      输出

      1. loaded mysql driver
      2. loaded oracle driver

      ServiceLoader简析,查看JDK1.8的源码
      ServiceLoader.png
      主要成员变量 ```java // 默认扫描路径 private static final String PREFIX = “META-INF/services/“;

    // 被加载的类或者接口 private final Class service;

    // 用于定位、加载和实例化实现方实现的类的类加载器 private final ClassLoader loader;

    // ServiceLoader被创建后的上下文对象 private final AccessControlContext acc;

    // 按照实例化的顺序缓存已经实例化的类 private LinkedHashMap providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator 懒加载器 private LazyIterator lookupIterator;

    1. 当调用静态load方法时,会创建一个新的ServiceLoader对象。
    2. ```java
    3. public static <S> ServiceLoader<S> load(Class<S> service,
    4. ClassLoader loader)
    5. {
    6. return new ServiceLoader<>(service, loader);
    7. }

    私有化构造方法初始化,做加载类判空,如果没有自定义类加载器,使用系统类加载器,并且初始化上下文对象,调用reload方法。

    1. private ServiceLoader(Class<S> svc, ClassLoader cl) {
    2. service = Objects.requireNonNull(svc, "Service interface cannot be null");
    3. loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    4. acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    5. reload();
    6. }

    reload方法中,清理服务提供缓存,创建懒加载迭代器。此时并不会加载服务对象。

    1. public void reload() {
    2. providers.clear();
    3. lookupIterator = new LazyIterator(service, loader);
    4. }

    由于LazyIterator实现了Iterator接口,当通过迭代器循环时,扫描获取所有的服务类并装载到缓存中,供下次使用。

    1. private S nextService() {
    2. if (!hasNextService())
    3. throw new NoSuchElementException();
    4. String cn = nextName;
    5. nextName = null;
    6. Class<?> c = null;
    7. try {
    8. c = Class.forName(cn, false, loader);
    9. } catch (ClassNotFoundException x) {
    10. fail(service,
    11. "Provider " + cn + " not found");
    12. }
    13. if (!service.isAssignableFrom(c)) {
    14. fail(service,
    15. "Provider " + cn + " not a subtype");
    16. }
    17. try {
    18. S p = service.cast(c.newInstance());
    19. providers.put(cn, p);
    20. return p;
    21. } catch (Throwable x) {
    22. fail(service,
    23. "Provider " + cn + " could not be instantiated",
    24. x);
    25. }
    26. throw new Error(); // This cannot happen
    27. }

    上面简单分析了Java SPI的实现ServiceLoader,借助于这种思想,每种框架的实现方式大同小异,比如Spring Boot Starter还引入了注解扫描,注册,按需加载的实现形式。

    建议阅读JDK11的实现代码,更为巧妙。
    ServiceLoader.png

    完整代码示例:https://gitee.com/starsray/test/tree/master/spi-test