Java SPI原理解析

980 阅读2分钟

What is SPI?

Wiki:Service provider interface (SPI) is an API intended to be implemented or extended by a third party. It can be used to enable framework extension and replaceable components.

服务提供者接口( SPI ) 是一种旨在由第三方实现或扩展的API。它可用于启用框架扩展和可替换组件。

Ex

  1. 创建一个接口/抽象类
  2. 实现该接口/继承抽象类
  3. resources 目录下创建 META-INF/services (后面会说为什么是这个目录)
  4. 创建文件(该接口/抽象类的全限定类名),文件内容为该接口/抽象类下的 实现/子类
  5. 使用 ServiceLoad.load(interface/abstract class) 加载

示例代码:Ex repo

原理解析

  • SPI 关键在于 ServiceLoad
private static final String PREFIX = "META-INF/services/";

我们可以看到 PREFIX 变量指定了该路径,也就是为什么要在 resource 下面创建该目录

  • load 方法
public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
    
    最后会调用 ServiceLoader 的构造方法
    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);
    }

    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
        }

最后可以发现使用的是 ClassLoader 进行加载,使用当前线程的 ClassLoader,如果等于 null,则使用 SystemClassLoader, 这里不再说明 ClassLoader双亲委派机制

观察 nextService方法,我们可以看到通过Class.forName() 进行加载类,因为我们之前在 resources 目录下定义了实现/继承类的全限定类名, 最后通过 c.newInstance() 进行实例化,至此我们完成了类的加载,以及类实例的创建

Why use SPI?

通过观察,我们发现SPI可以通过外部加载来提供扩展,比较灵活。

也因为外部加载使得依赖解耦,让类的实现/继承分离,在设计原则上这是一种实现扩展性的手段

总结

SPI 的原理其实就是类加载机制原理,通过类加载器加载类,然后在实例化类

通过 SPI PREFIX 变量我们也可以推论出,我们可以自己实现一套规则,加载自定义目录下的类

Dubbo RPC 也用了 SPI 机制,当然现在还没有深入研究去看实现原理,不过大致的思路也应该能猜到了

引用

en.wikipedia.org/wiki/Servic…

www.baeldung.com/java-spi