1. SPI概述
1.1 什么是SPI
SPI全称Service Provider Interface,是一种服务发现机制。为了被第三方实现或扩展的 API,它可以用于实现框架扩展或组件替换。
SPI 机制本质是将 接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载文件中的实现类,这样运行时可以动态的为接口替换实现类。
SPI本质上就是动态指定接口的实现类,比如FileStoreService有3个实现类,具体要使用哪一个实现类可以通过SPI动态的获取某一个实现类。
SPI实现了接口与实现类的解耦,提高了应用的可扩展性。
具体步骤就是,首先创建一个接口与多个实现类,然后再META-INF/services目录下创建接口的权限定名称文件,配置内容是接口实现类的权限定名称,这样就可以通过Service.load获取所有的实现类,根据配置获取指定实现类。
1.2 适用场景
SPI 最合适的还是没有统一业务实现场景,就像通过sharding-jdbc的数据加密算法就是通过SPI方式扩展的,其中还有dubbo的SPI等等。
2. 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 {
//获取目录下所有的类 转换为-URL,工作流的方式获取每一行的数据并截取第一个‘#’之前的数据,并去除前后的空格
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 {
//反射加载类 将上面获取的url进行转换为对象类
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) {
//..
}
//..
}
}
}
3.SPI案例
3.1 项目需求
这里举的例子是存储服务,实现可以支持不同云服务的存储,当然需求可以有很多实现方式,策略设计模式亦可以,SPI也没问题,只是为了说明问题。
为了提高可扩展性,支持不同云存储,采用SPI动态加载不同服务实现类。
3.2 项目结构
3.3 项目地址
gitee地址:https://gitee.com/rjzhu/spi/tree/master
application.properties
file.store.type=OSS
路径:META-INF.services
文件名:com.hello.spi.service.FileStoreService
com.hello.spi.service.impl.COSFileStoreServiceImpl
com.hello.spi.service.impl.OBSFileStoreServiceImpl
com.hello.spi.service.impl.OSSFileStoreServiceImpl
Constants
package com.hello.spi.common;
/**
* @author zrj
* @since 2021/6/28
**/
public class Constants {
/**
* 阿里云OSS
*/
public static final String OSS_FILE_STORE = "OSS";
/**
* 腾讯云COS
*/
public static final String COS_FILE_STORE = "COS";
/**
* 华为云OBS
*/
public static final String OBS_FILE_STORE = "OBS";
}
FileStoreController
package com.hello.spi.controller;
import com.hello.spi.factory.FileServiceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* SPI测试控制器
*
* @author zrj
* @since 2021/6/28
**/
@RestController
@RequestMapping("/file")
public class FileStoreController {
@Autowired
private FileServiceFactory fileServiceFactory;
/**
* SPI测试,文件下载
*/
@GetMapping("download")
public String fileDownload() {
fileServiceFactory.download("");
return "hello SPI download";
}
/**
* SPI测试,文件上传
*/
@GetMapping("upload")
public String fileUpload() {
InputStream inputStream = new ByteArrayInputStream("".getBytes());
fileServiceFactory.upload(inputStream);
return "hello SPI upload";
}
}
FileServiceFactory
package com.hello.spi.factory;
import com.hello.spi.service.FileStoreService;
import lombok.var;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
/**
* @author zrj
* @since 2021/6/28
**/
@Component
public class FileServiceFactory implements InitializingBean {
@Value("${file.store.type:OSS}")
private String fileStoreType;
private FileStoreService fileStoreService;
private Map<String, FileStoreService> fileStoreServiceMap = new HashMap<>();
public Object upload(InputStream inputStream) {
return fileStoreService.upload(inputStream);
}
public Object download(String fileId) {
return fileStoreService.download(fileId);
}
@Override
public void afterPropertiesSet() throws Exception {
var serviceLoader = ServiceLoader.load(FileStoreService.class);
var fileStoreServiceIterator = serviceLoader.iterator();
while (fileStoreServiceIterator.hasNext()) {
var customerFileStoreService = fileStoreServiceIterator.next();
fileStoreServiceMap.put(customerFileStoreService.getType(), customerFileStoreService);
}
fileStoreService = fileStoreServiceMap.get(fileStoreType);
}
}
FileStoreService
package com.hello.spi.service;
import java.io.InputStream;
/**
* 存储服务接口
*
* @author zrj
* @since 2021/6/28
**/
public interface FileStoreService {
/**
* 文件上传
*
* @param inputStream 文件流
* @return Object 返回对象
*/
Object upload(InputStream inputStream);
/**
* 文件上传
*
* @param fileId 文件ID
* @return Object 返回对象
*/
Object download(String fileId);
/**
* 存储类型
*/
String getType();
}
3个实现类都是空方法,只是为了验证SPI,就不在列举了,具体建gitee。