【SPI】Java的SPI机制

123 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情

SPI是什么

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

使用SPI机制的好处就在于使得模块间能基于接口编程,而不用对实现类进行硬编码。

Java SPI的使用

步骤

  1. 接口实现类需要在jar包的META-INF/services目录下创建一个文件,文件名为“接口全限定名”,内容为实现类的全限定名。
  2. 接口实现类必须带一个无参构造器。
  3. 通过java.util.ServiceLoder#load(...)方法可以加载实现类对象。

Demo

接口:

package world.shuashua;
public interface Handler {
    void doHandler();
}

实现类-1:

package world.shuashua;
public class HandlerImplOne implements Handler {
    public void doHandler() {
        System.out.println("一号机正在处理");
    }
}

实现类-2:

package world.shuashua;
public class HandlerImplTwo  implements Handler {
    public void doHandler() {
        System.out.println("二号机正在处理");
    }
}

配置文件:

使用ServiceLoader#load(...)方法加载实现类

package world.shuashua;
import java.util.ServiceLoader;

public class Main {
    public static void main(String[] args) {
        ServiceLoader<Handler> handlers = ServiceLoader.load(Handler.class);
        for (Handler handler : handlers) {
            handler.doHandler();
        }
    }
}

原理剖析

ServiceLoder成员变量

// 加载的配置文件前缀,这也就是为什么文件要建在META-INF/services/目录下。
// 另外这个路径是final修饰的,并且已赋值,是不可更改的。
private static final String PREFIX = "META-INF/services/";

// 需要被加载和实例化的接口或类
private final Class<S> service;

// 加载和实例化providers的类加载器
private final ClassLoader loader;

// 创建ServiceLoader时采用的访问控制上下文
private final AccessControlContext acc;

// providers的缓存, 使用LinkedHashMap能按顺序存储实例化的顺序。
// providers:<实现类全限定名,实现类的实例对象>
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 懒加载的查找迭代器
private LazyIterator lookupIterator;

ServiceLoder#load(...)方法调用过程分析

分析:ServiceLoader handlers = ServiceLoader.load(Handler.class);

  1. ServiceLoader#load(Class<S> service)
  2. ServiceLoader#load(Class<S> service,ClassLoader loader)
  3. ServiceLoader#ServiceLoader(Class<S> svc, ClassLoader cl) ->构造器方法
  4. ServiceLoader#reload()

ServiceLoader#load(...)

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

ServiceLoader#ServiceLoader(...) 【私有的构造器方法】

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

ServiceLoader#reload()

public void reload() {
    providers.clear();  // 清空map
    lookupIterator = new LazyIterator(service, loader); // 创建懒加载的迭代器对象。此时并未加载类和实例化对象
}

实例化对象的时机

private S nextService() {
    if (!hasNextService()) // 如果没有下一个实现类了
        throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        c = Class.forName(cn, false, loader); // 使用loader加载实现类的Class对象,但不初始化
    } 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); // 将 <实现类全限定名,实现类的实例对象> 放入providers
        return p;
    } catch (Throwable x) {
        fail(service,"Provider " + cn + " could not be instantiated",x);
    }
    throw new Error();          // This cannot happen
}

缺点

  1. 不能按需加载,虽然其存在懒加载机制。
  2. 多个并发多线程使用ServiceLoader类的实例是不安全的