参考文献

SPI机制

  • JDK SPI(Service Provider Interface)机制是一种基于接口的服务发现机制,它允许应用程序在运行时动态地查找和加载实现某个接口的服务提供者。

    • 在每次类加载的时候会先去找到 class 相对目录下的 META-INF 文件夹下的 services 文件夹下的文件,将这个文件夹下面的所有文件先加载到内存中,然后根据这些文件的文件名和里面的文件内容找到相应接口的具体实现类,找到实现类后就可以通过反射去生成对应的对象,保存在一个 list 列表里面,所以可以通过迭代或者遍历的方式拿到对应的实例对象,生成不同的实现。
  • JDK SPI 机制的基本原理如下:

    1. 定义接口:定义一个接口,其实现类将作为服务提供者。
    2. 配置文件:在 META-INF/services目录下创建一个以接口全限定名命名的文本文件,该文件的内容是实现该接口的服务提供者的全限定名。
      • 文件名一定要是接口的全类名,然后里面的内容一定要是实现类的全类名,实现类可以有多个,直接换行就好了,多个实现类的时候,会一个一个的迭代加载。
    3. 服务加载:Java 应用程序通过ServiceLoader类的静态方法load()加载指定接口的服务提供者。
    4. 服务发现:通过遍历服务提供者配置文件,ServiceLoader 类可以查找和加载实现该接口的服务提供者,并返回一个包含这些服务提供者实例的迭代器。
  • JDK SPI 机制的优点在于,它可以在不修改代码的情况下,动态地加载和替换服务提供者,从而实现了松耦合,增强了程序的可扩展性和灵活性。它也广泛应用于 Java 中的各种框架和组件中,如日志框架、数据库驱动、HTTP 客户端等。

示例

  • 创建一个Log接口

    1
    2
    3
    4
    5
    6
    7
    8
    package com.holelin.sundry.test.spi;

    /**
    * @author HoleLin
    */
    public interface Log {
    void log(String info);
    }
  • 实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.holelin.sundry.test.spi;

    /**
    * @author HoleLin
    */
    public class Log4j implements Log {
    @Override
    public void log(String info) {
    System.out.println("Log4j" + info);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.holelin.sundry.test.spi;

    /**
    * @author HoleLin
    */
    public class Logback implements Log {
    @Override
    public void log(String info) {
    System.out.println("Logback" + info);
    }
    }
  • 在项目的resources/META-INF/services目录下添加一个名为com.holelin.sundry.test.spi.Log的文件,这是JDK SPI需要读取的配置文件,内容如下:

    1
    2
    com.holelin.sundry.test.spi.Logback
    com.holelin.sundry.test.spi.Log4j
  • main方法,其中会加载上述配置文件,创建全部 Log 接口实现的实例,并执行其 log() 方法,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package com.holelin.sundry.test.spi;

    import java.util.ServiceLoader;

    /**
    * @author HoleLin
    */
    public class Main {
    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");
    }
    }
    }

    // 输出如下:
    // Log4j:JDK SPI
    // Logback:JDK SPI

源码解析

  • 基于JDK8

  • ServiceLoader.load() 方法中,首先会尝试获取当前使用的 ClassLoader(获取当前线程绑定的 ClassLoader,查找失败后使用 SystemClassLoader),然后调用 reload() 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
    }

    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();
    }

    public void reload() {
    // 清空缓存
    providers.clear();
    // 迭代器
    lookupIterator = new LazyIterator(service, loader);
    }
  • main() 方法中使用的迭代器底层就是调用了 ServiceLoader.LazyIterator 实现的。Iterator 接口有两个关键方法:hasNext() 方法和 next() 方法。这里的 LazyIterator 中的next() 方法最终调用的是其 nextService() 方法,hasNext() 方法最终调用的是 hasNextService() 方法

  • LazyIterator.hasNextService() 方法,该方法主要负责查找 META-INF/services 目录下的 SPI 配置文件,并进行遍历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    private boolean hasNextService() {
    if (nextName != null) {
    return true;
    }
    if (configs == null) {
    try {
    // PREFIX = "META-INF/services/"
    // PREFIX前缀与服务接口的名称拼接起来,就是META-INF目录下定义的SPI配
    // 置文件(即示例中的META-INF/services/com.xxx.Log)
    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);
    }
    }
    // 按行SPI遍历配置文件
    while ((pending == null) || !pending.hasNext()) {
    if (!configs.hasMoreElements()) {
    return false;
    }
    // 解析配置文件
    pending = parse(service, configs.nextElement());
    }
    // 更新nextName字段
    nextName = pending.next();
    return true;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    private S nextService() {
    if (!hasNextService())
    throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
    // 加载nextName字段指定的类
    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
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public Iterator<S> iterator() {
    return new Iterator<S>() {

    // knownProviders用来迭代providers缓存
    Iterator<Map.Entry<String,S>> knownProviders
    = providers.entrySet().iterator();

    public boolean hasNext() {
    // 先查询缓存,缓存查询失败,再通过LazyIterator加载
    if (knownProviders.hasNext())
    return true;
    return lookupIterator.hasNext();
    }

    public S next() {
    // 先查询缓存,缓存查询失败,再通过LazyIterator加载
    if (knownProviders.hasNext())
    return knownProviders.next().getValue();
    return lookupIterator.next();
    }

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

    };
    }