「周末福报」你了解 SPI 吗?

时间:2022-07-24
本文章向大家介绍「周末福报」你了解 SPI 吗?,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

洛克李:如果没办法连续做500下伏地挺身的话,就要跳绳1200次!如果不能连续跳绳1200下的话,就要踢木头2000次!


正文开始

SPI(Service Provider Interface)是一种 JDK 提供的服务发现机制。

它通过读取指定( Classpath 路径下的 META/services)目录下(以服务命名)的文件,来自动加载文件中的定义类。

例如:

我们在对数据库进行操作时,经常会使用到 java.sql.Driver 接口。然而,数据库厂商们提供的协议都不一样,提供的 java.sql.Driver 实现也不同。

程序员在使用 java.sql.Driver 进行开发时,并不知道用户最终会使用哪个数据库。

这时候,就可以使用 Java 提供的 SPI 机制,来寻找 java.sql.Driver 接口对应的实现类。


示例代码

首先创建一个日志接口类,用来模拟日志打印功能。

package cn.live.service;

public interface Log {
    void log(String msg);
}

提供 Log4j、LogBack 两个实现类,代表两种日志框架的不同实现。

public class Log4j implements Log {
    @Override
    public void log(String msg) {
        System.out.println(String.format("Log4j: %s", msg));
    }
}

public class LogBack implements Log {
    @Override
    public void log(String msg) {
        System.out.println(String.format("LogBack: %s", msg));
    }

新建 META-INF/services 目录,并添加一个 cn.live.service.Log 文件。

cn.live.service.Log 文件内容为该接口实现类文件的全路径。

cn.live.service.impl.LogBack
cn.live.service.impl.Log4j

新建一个测试类,进行验证。

public class LogTest {
    public static void main(String[] args) {
        ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
        Iterator<Log> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            Log log = iterator.next();
            log.log("JDK SPI");
        }
    }
}

控制台输出了 LogBack、Log4j 的不同实现信息。

以上,通过简单的打印日志示例代码,展示了 SPI 机制的简单运用。


JDK SPI 源代码

使用当前线程的 ClassLoader

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

首先,会检查传入的接口类是否存在。

其次,当前线程的 ClassLoader 不存在时,默认使用 SystemClassLoader。

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
    return new ServiceLoader<>(service, loader);
}

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

首先,清理 LinkedHashMap 缓存。

其次,初始化新的 LazyIterator 对象,实现完全延迟的查找。

private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}

LazyIterator 是私有的内部类,实现完全延迟的提供程序查找。

  • hasNextService 会读取固定前缀 PREFIX 服务接口文件数据,并将读到的数据赋值给 pending 对象。
  • nextService 会将 hasNextService 方法读取到的信息,转换成实现类 Class,赋值给 providers 对象。
private class LazyIterator implements Iterator<S> {
  Class<S> service;
  ClassLoader loader;
  Enumeration<URL> configs = null;
  Iterator<String> pending = null;
  String nextName = null;

  private LazyIterator(Class<S> service, ClassLoader loader) {
      this.service = service;
      this.loader = loader;
  }

  private boolean hasNextService() {
      if (nextName != null) {
          return true;
      }
      if (configs == null) {
          try {
              String fullName = PREFIX + service.getName();
              if (loader == null)
                  configs = ClassLoader.getSystemResources(fullName);
              else
                  configs = loader.getResources(fullName);
          } catch (IOException x) {
              fail(service, "Error locating configuration files", x);
          }
      }
      while ((pending == null) || !pending.hasNext()) {
          if (!configs.hasMoreElements()) {
              return false;
          }
          pending = parse(service, configs.nextElement());
      }
      nextName = pending.next();
      return true;
  }

  private S nextService() {
      if (!hasNextService())
          throw new NoSuchElementException();
      String cn = nextName;
      nextName = null;
      Class<?> c = null;
      try {
          c = Class.forName(cn, false, loader);
      } catch (ClassNotFoundException x) {
          fail(service,
               "Provider " + cn + " not found");
      }
      if (!service.isAssignableFrom(c)) {
          fail(service,
               "Provider " + cn  + " not a subtype");
      }
      try {
          S p = service.cast(c.newInstance());
          providers.put(cn, p);
          return p;
      } catch (Throwable x) {
          fail(service,
               "Provider " + cn + " could not be instantiated",
               x);
      }
      throw new Error();          // This cannot happen
  }

  public boolean hasNext() {
      if (acc == null) {
          return hasNextService();
      } else {
          PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
              public Boolean run() { return hasNextService(); }
          };
          return AccessController.doPrivileged(action, acc);
      }
  }

  public S next() {
      if (acc == null) {
          return nextService();
      } else {
          PrivilegedAction<S> action = new PrivilegedAction<S>() {
              public S run() { return nextService(); }
          };
          return AccessController.doPrivileged(action, acc);
      }
  }

  public void remove() {
      throw new UnsupportedOperationException();
  }

}

小结

Java 很多知识点,说明白之后。其实非常的简单。

就是要不停的去回顾,再学习。

如果这周没有再去回顾 SPI,那么突然问我 “你了解 SPI 吗?简单介绍一下 SPI,原理是什么?有哪些经典的运用?”

我:一脸懵逼

(一首《凉凉》送给自己)

这个周末,又一次成功“强迫”自己学习。

感谢各位小伙伴的阅读,这里是一个技术人的学习与分享。