Java SPI机制

1. 什么是SPI

SPI 全称 Service Provider Interface,是 Java 提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI 的作用就是为这些被扩展的 API 寻找服务实现。

2. SPI和API的使用场景

API (Application Programming Interface) 在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。

SPI (Service Provider Interface) 是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。

3. SPI的简单实现

3.1 首先定义一组接口

public interface JavaSPITest {
    void spiTest(String msg);
}

3.2 编写接口实现(可以有多个)

这里我定义了两个实现

public class JavaSPITest implements com.lizq.JavaSPITest {

    @Override
    public void spiTest(String msg) {
        System.out.println("JavaSPITest:" + msg);
    }
}
public class JavaSPITest2 implements com.lizq.JavaSPITest {

    @Override
    public void spiTest(String msg) {
        System.out.println("JavaSPITest2:" + msg);
    }
}

3.3 新建META-INF/services目录及对应文件

在 resources 目录下新建 META-INF/services 目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件,在这个文件中写入接口的实现类的全限定名:
Java SPI机制
Java SPI机制

3.4 通过ServiceLoader加载实现类并调用

ServiceLoader<JavaSPITest> spiTests = ServiceLoader.load(JavaSPITest.class);
for (JavaSPITest u : spiTests) {
	u.spiTest("Test Java SPI");
}

这样一个简单的spi的demo就完成了。可以看到其中最为核心的就是通过ServiceLoader这个类来加载具体的实现类的。

4. SPI原理解析

public final class ServiceLoader<S> implements Iterable<S> {
    //扫描目录前缀
    private static final String PREFIX = "META-INF/services/";

    // 被加载的类或接口
    private final Class<S> service;

    // 用于定位、加载和实例化实现方实现的类的类加载器
    private final ClassLoader loader;

    // 上下文对象
    private final AccessControlContext acc;

    // 按照实例化的顺序缓存已经实例化的类
    private LinkedHashMap<String, S> providers = new LinkedHashMap<>();

    // 懒查找迭代器
    private java.util.ServiceLoader.LazyIterator lookupIterator;

    // 私有内部类,提供对所有的service的类的加载与实例化
    private class LazyIterator implements Iterator<S> {
        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        String nextName = null;

        //...
        private boolean hasNextService() {
            if (configs == null) {
                try {
                    //获取目录下所有的类
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    //...
                }
                //....
            }
        }

        private S nextService() {
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //反射加载类
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
            }
            try {
                //实例化
                S p = service.cast(c.newInstance());
                //放进缓存
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                //..
            }
            //..
        }
    }
}

参考文档:
https://www.cnblogs.com/jy107600/p/11464985.html

上一篇:java spi 机制案例详解


下一篇:Java编写画图板程序细节-保存已画图形