JAVA SPI 机制到底是什么?

SPI (Service Provider Interface) 是一种将服务接口和服务实现分离的机制
这样一来便做到了降低耦合度增加可扩展性的目的,当然最重要的是实现了程序之间的可插拔,像jdbc的驱动加载,dubbo的实现,springBoot自动配置都是基于了spi这种机制,再次基础上加以扩展的.

java原生SPI实现:

创建一个接口与其的2个实现类:

public interface SpiInterface {
    /***
     * Description <br>
     *  方法调用
     **/
    void action();
}
public class OriginalImpl implements SpiInterface {
    @Override
    public void action() {
        System.out.println("OriginalImpl");
    }
}
public class DubboImpl implements SpiInterface {
    @Override
    public void action() {
        System.out.println("DubboImpl");
    }
}
在classpath下创建META-INF\services\ 目录(这个名称不能错,后面会解释) 将接口的全类名作为文件放入该目录下

Java的di是什么 java的dispose_class


将实现类全类名分行写入该文件中


Java的di是什么 java的dispose_spi_02

编写测试类:

public class SpiTest {
    public static void main(String[] args) {
        ServiceLoader<SpiInterface> serviceLoader = ServiceLoader.load(SpiInterface.class);
        serviceLoader.forEach(SpiInterface::action);
    }
}
测试结果:

Java的di是什么 java的dispose_class_03

  • 可以看到使用java原生的spi虽然平没有显示实例化实现类但是将文件中的实现都实例化并调用了action打印了结果.
  • 分析下load()方法干了什么:
private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(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);
    }
  1. 判断下是否传入了加载器,没有的话使用系统加载器,
  2. 接着回去调用reload方法而这个reload()又会去调用LazyIterator,其实也就是这是一个延迟的加载ServiceLoader.load().
  3. LazyIterator这个装载类的时候没有立马实例化我们需要的对象,而是等到调用迭代器的hasNext时候才回去实例化我们的接口实现对象,
  4. 具体怎么实现,因为读取的配置全类名,那么自然是使用的java反射调用构造器进行的实例化.

DUBBO的SPI

SPI在dubbo中也是有十分重要的使用体现,如dubbo调用策略的切换,使用协议的切换都依赖于SPI机制,而dubbo的SPI是在源生的SPI上做了扩展,弥补了源生SPI不能定向实例化的不足,还是使用之前的Service类,编写dubbo SPI测试类
注: 使用DUBBO SPI需要在接口加上@SPI注解

@SPI
public interface SpiInterface {
    /***
     * Description <br>
     *  方法调用
     **/
    void action();
}
在classpath下创建META-INF\dubbo\ 目录,将接口的全类名作为文件放入该目录下 将实现类全类名以key-value分行写入该文件中

Java的di是什么 java的dispose_spi_04


Java的di是什么 java的dispose_java_05

测试类:

public class DubboSpiTest {
    public static void main(String[] args) {
        ExtensionLoader<SpiInterface> extensionLoader = ExtensionLoader.getExtensionLoader(SpiInterface.class);
        SpiInterface dubbo = extensionLoader.getExtension("dubbo");
        SpiInterface original = extensionLoader.getExtension("original");
        dubbo.action();
        original.action();
    }
}
  • dubbo的SPI是使用ExtensionLoader扩展了java源生的SPI,可以实现以key-value的形式加载接口实现类.