一、SPI介绍
SPI全称Service Provider Interface,是Java的一套用来让第三方提供接口实现
或者扩展接口的机制。
SPI可以很灵活的让接口和实现分离,让api提供者只提供接口,第三方来实现,然后可以使用配置文件的方式来实现替换或者扩展,在框架中比较常见,提高框架的可扩展性。
二、SPI和API区别
API: 当实现方提供了接口和实现,我们可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是 API ,这种接口和实现都是放在实现方的。(如:AJAX向后端请求)
当接口存在于调用方这边时,就是 SPI ,由接口调用方确定接口规则,然后由不同的厂商去根据这个规则对这个接口进行实现,从而提供服务。
举个通俗易懂的例子:公司 H 是一家科技公司,新设计了一款芯片,然后现在需要量产了,而市面上有好几家芯片制造业公司,这个时候,只要 H 公司指定好了这芯片生产的标准(定义好了接口标准),那么这些合作的芯片公司(服务提供者)就按照标准交付自家特色的芯片(提供不同方案的实现,但是给出来的结果是一样的)。
三、Java SPI机制–ServiceLoader
ServiceLoader是Java提供的一种简单的SPI机制的实现,Java的SPI实现约定了以下三件事:
- 文件必须放在META-INF/services/目录底下
- 文件名必须为接口的全限定名
- 文件内容为接口实现类的全限定名
这样就能够通过ServiceLoader加载到文件中接口的实现。
四、代码实现
spi-interface
定制接口标准
public interface HelloSPI {
public void sayHello();
}
spi-provider
模拟第三方,实现接口。
写了两个实现HelloSPI接口的实现类
引入接口依赖
<dependency>
<groupId>com.it</groupId>
<artifactId>spi-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
ImageSPI (HelloSPI接口实现类)
public class ImageSPI implements HelloSPI {
@Override
public void sayHello() {
System.out.println("ImageSPI sayHello...");
}
}
TextSPI (HelloSPI接口实现类)
public class TextSPI implements HelloSPI {
@Override
public void sayHello() {
System.out.println("TextSPI sayHello...");
}
}
com.it.spi.HelloSPI文件
com.it.spi.impl.ImageSPI
com.it.spi.impl.TextSPI
spi-user
模拟调用者,需引入接口依赖和第三方实现的依赖
引入依赖
<dependency>
<groupId>com.it</groupId>
<artifactId>spi-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.it</groupId>
<artifactId>spi-privider</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
自定义加载类,将实现HelloSPI接口的第三方实现加载并实例化。
- META-INF/services/ 接口全限定名,找到该文件
- 读取文件里的内容(实现类的全限定名),并且实例化,放到集合中
public class MyServiceLoader<T> {
private Class service;
private ClassLoader classLoader;
private List<T> providers = new ArrayList<>();
public MyServiceLoader(Class service){
this.service = service;
this.classLoader = Thread.currentThread().getContextClassLoader();
load();
}
@SneakyThrows
private void load(){
Enumeration<URL> urls = classLoader.getResources("META-INF/services/" + service.getName());
while(urls.hasMoreElements()){
URL url = urls.nextElement();
String path = url.getPath();
FileReader fileReader = new FileReader(path);
BufferedReader reader = new BufferedReader(fileReader);
String className = reader.readLine();
while(className != null){
Class<?> aClass = Class.forName(className);
if (service.isAssignableFrom(aClass)){
T o = (T) aClass.newInstance();
providers.add(o);
}
className = reader.readLine();
}
}
}
public List<T> getProviders(){
return providers;
}
}
主程序调用自定义加载方法,获取第三方实现类并调用其方法
public class Main {
public static void main(String[] args) {
MyServiceLoader<HelloSPI> loader = new MyServiceLoader<>(HelloSPI.class);
List<HelloSPI> providers = loader.getProviders();
for (HelloSPI provider : providers) {
provider.sayHello();
}
}
}