缘起

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

说人话就是可以动态的配置接口的实现类

SPI的使用场景

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

说人话就是框架提供了一个接口,然后你可以写实现类,你的实现类会被框架所执行,所以这个东西框架里面用的比较多

如何实现一个spi

新建一个maven项目test-spi

Java SPI 机制详解_加载

新建接口SayHelloI

public interface SayHelloI {
void sayHello();
}

新建接口的实现类SayHelloImpl

public class SayHelloImpl implements SayHelloI {
@Override
public void sayHello() {
System.out.println("Hello");
}
}

在resources下新建META-INF文件夹再新建services文件夹,在service文件夹下面新建一个文件com.bxoon.demo1.SayHelloI

注意:路径结构不能错,并且文件名就是对应接口的全路径名

新建一个TestMain,来进行测试

public class TestMain {

public static void main(String[] args) {
ServiceLoader<SayHelloI> serviceLoader = ServiceLoader.load(SayHelloI.class);
for (SayHelloI sayHello:serviceLoader){
sayHello.sayHello();
}
}
}

控制台输出

Java SPI 机制详解_spi_02

到此,一个spi程序就搭建执行成功了,好像觉得没什么卵用。好处主要有以下几个方面


  • 不需要改动源码就可以实现扩展,解耦。
  • 实现扩展对原来的代码几乎没有侵入性。
  • 只需要添加配置就可以实现扩展,符合开闭原则。

SPI的实现原理

通过上面简单的demo,可以看到最关键的实现就是ServiceLoader这个类,可以看下这个类的源码,如下:

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) {
//..
}
//..
}
}
}

Java SPI 机制详解_实例化_03