Java SPI

一、简介

SPI全称Service Provider Interface,它是JDK内置的一种可以动态发现服务的机制。通过这种方式,可以方便地将服务提供者与第三方实现客户端解耦。它主要包含三个基本组件:服务接口,提供者注册API以及服务访问API

下面是Wikipedia的解释

服务是一组众所周知的接口和(通常是抽象的)类。服务提供者是服务的特定实现。提供程序中的类通常实现接口并子类化服务本身中定义的类。服务提供者可以以扩展的形式安装在Java平台的实现中,即,将jar文件放入任何通常的扩展目录中。通过将提供者添加到应用程序的类路径或通过其他一些特定于平台的方法,也可以使提供者可用。

二、使用场景

Java SPI实际上就是“面向接口编程+策略模式+配置文件”组合实现动态加载机制,多用于各种框架中,通过暴露扩展点,实现对框架特定的点进行定制,提升框架灵活性。

1. SPI应用实例

  • Java SQL Driver(MySQL, Oracle, SqlServer)
  • SLF4J(log4j, logback)
  • Dubbo
  • Spring(Converter, Formatter)

三、示例

1. 定义一个接口

public interface ProgramLanguage {

    String hello();

}

2. 编写接口实现类

public class Chinese implements Language {

    @Override
    public String hello() {
        return "你好";
    }

}

public class English implements Language {
    @Override
    public String hello() {
        return "Hello";
    }
}

3. 配置文件

创建/resources/META-INF/services,并在services文件夹下创建以接口全限定名称命名的文件,并在文件中写入实现类的全限定名

com.example.spi.api.impl.English
com.example.spi.api.impl.Chinese

4. 测试

public class App {

    public static void main(String[] args) {
        ServiceLoader loader = ServiceLoader.load(Language.class);
        for (Language language : loader) {
            System.out.println(language.hello());
        }
    }
}

输出

Hello
你好

完整代码放在GitHub

四、原理

首先看下ServiceLoader继承关系,从这里看出为什么我们可以对loader对象进行foreach操作
Java SPI_第1张图片

接下来看iterator方法,返回了一个Iterator实例

private LinkedHashMap providers = new LinkedHashMap<>();

private LazyIterator lookupIterator;

public Iterator iterator() {
    return new Iterator() {
        Iterator> knownProviders
            = providers.entrySet().iterator();
        public boolean hasNext() {
            // 缓存已加载的实例
            if (knownProviders.hasNext())
                return true;
            // 查找实例
            return lookupIterator.hasNext();
        }
        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }
        public void remove() {
            throw new UnsupportedOperationException();
        }
    };
}

继续跟踪lookupIterator.hasNext()

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

private static final String PREFIX = "META-INF/services/";

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

从上面的代码可以看出,ServiceLoader读取文件的内容,并尝试将读取到的每一行加载对应类,并实例化对象,这样便完成了服务查找过程。

注意,由于使用这里通过newInstance()方法实例化对象(即通过默认构造方法创建对象),所以Service必须保留默认构造方法。

更多内容请关注我的博客
本文由博客一文多发平台 OpenWrite 发布!

你可能感兴趣的:(Java SPI)