一、概述
SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。可以用来启用框架扩展和替换组件, 主要是被框架的开发人员使用
比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现, MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制 主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦。
SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
SPI整体机制图如下:
将目录结构按照如下的方式建立,并按照规范填写响应的内容即可完成spi的自动发现。
文件结构主要是 文件名(实现的接口名称) + 文件内容(code+type具体实现类的权限定名)
code 要和 我们之前实现的工厂内置的code一致
二、实现步骤
1、在Configuration配置类中加载spi机制
public Configuration() {
//1.成员变量的默认配置项
//2.spi 机制发现相关配置项
SpiResolver spiResolver = new SpiResolver();
spiResolver.loadFromSpi(this);
//3.读取xml获取上边的信息
XmlResolver xmlResolver = new XmlResolver();
xmlResolver.loadFromXml(this);
//4.编程配置项:rrpcbootstarp提供
}
2、SpiHandler
实现spi机制的主要代码,用于获取和解析services下的文件,并创建缓存,避免重复读取文件内容
* 实现一个简易版本的spi
*/
@Slf4j
public class SpiHandler {
//创建spi缓存,保存spi相关的原始内存
private static final Map<String, List<String>> SPI_CONTENT = new ConcurrentHashMap<>(8);
//保存我们的spi文件
private static final String BASE_PATH = "META-INF/rrpc-services";
//创建实例的缓存
public static final Map<Class<?>, List<ObjectWrapper<?>>> SPI_IMPLEMENT = new ConcurrentHashMap<>(8);
//加载当前工程的jar包中的classPath资源,加载后将spi信息保存,避免重复io
static {
//1.使用classLoader类加载机制进行加载 PATH路径下的文件
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL fileUrl = classLoader.getResource(BASE_PATH);
if (fileUrl != null) {
//获取到的文件是多个 用数组的方式去保存
File file = new File(fileUrl.getPath());
File[] children = file.listFiles();
if (children != null && children.length > 0) {
for (File child : children) {
String key = child.getName();
List<String> value = getImplNames(child);
SPI_CONTENT.put(key, value);
}
}
}
}
/**
* 获取第一个和当前服务相关的实例
*
* @param clazz 一个服务接口的class实例
* @param <T>
* @return
*/
public synchronized static <T> ObjectWrapper<T> get(Class<T> clazz) {
//1.优先读取缓存,缓存中没有的话再创建实例
List<ObjectWrapper<?>> objectWrappers = SPI_IMPLEMENT.get(clazz);
if (objectWrappers != null && objectWrappers.size() > 0) {
return (ObjectWrapper<T>) objectWrappers.get(0);
}
//2.构建缓存
buildCache(clazz);
List<ObjectWrapper<?>> result = SPI_IMPLEMENT.get(clazz);
if (result == null || result.size() == 0) {
return null;
}
//3.再次尝试获取第一个
return (ObjectWrapper<T>) result.get(0);
}
/**
* 获取所有和当前服务相关的实例
*
* @param clazz 一个服务接口的class实例
* @param <T>
* @return 实现类的实例集合
* synchronized用于线程安全的保护,避免拿不到而且修改变量的情况
*/
public synchronized static <T> List<ObjectWrapper<T>> getList(Class<T> clazz) {
//1.优先读取缓存,缓存中没有的话再创建实例
List<ObjectWrapper<?>> objectWrappers = SPI_IMPLEMENT.get(clazz);
if (objectWrappers != null && objectWrappers.size() > 0) {
//通过lamda表达式对集合的每个元素进行处理
return objectWrappers.stream().map(wrapper -> (ObjectWrapper<T>) wrapper)
.collect(Collectors.toList());
}
//2.构建缓存
buildCache(clazz);
//3.再次获取
objectWrappers = SPI_IMPLEMENT.get(clazz);
//通过lamda表达式对集合的每个元素进行处理
if (objectWrappers != null && objectWrappers.size() > 0) {
return objectWrappers.stream().map(wrapper -> (ObjectWrapper<T>) wrapper)
.collect(Collectors.toList());
}
return new ArrayList<>();
}
/**
* 构建clazz相关的缓存
* @param clazz 一个类的clazz实例
*/
private static void buildCache (Class < ? > clazz){
//1.通过clazz获取与之匹配的实现名称
String name = clazz.getName();
List<String> implNames = SPI_CONTENT.get(name);
if (implNames == null || implNames.size() == 0) {
return;
}
//2.实例化所有的实现
List<ObjectWrapper<?>> impls = new ArrayList<>();
//通过反射的创建实例
for (String implName : implNames) {
try {
//首先进行分割
String[] codeAndTypeAndName = implName.split("-");
if (codeAndTypeAndName.length != 3){
throw new SpiExcecption("您配置的spi文件不合法");
}
//将三个元素取出
Byte code = Byte.valueOf(codeAndTypeAndName[0]);
String type = codeAndTypeAndName[1];
String implementName = codeAndTypeAndName[2];
Class<?> aClass = Class.forName(implementName);
Object impl = aClass.getConstructor().newInstance();
ObjectWrapper objectWrapper = new ObjectWrapper<>(code,type,impl);
impls.add(objectWrapper);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
IllegalAccessException |
InvocationTargetException e) {
log.error("实例化【{}】的实现时发生异常", implName, e);
}
}
SPI_IMPLEMENT.put(clazz, impls);
}
/**
* 工具类 获取文件内所有的实现名称
* @param child 文件对象
* @return 实现类的权限定名称
*/
private static List<String> getImplNames (File child){
//读取文件中的内容
try (
FileReader fileReader = new FileReader(child);
//使用装饰器设计模式 将fileReader装饰成 BufferedReader
BufferedReader bufferedReader = new BufferedReader(fileReader);
) {
List<String> lineNames = new ArrayList<>();
while (true) {
String line = bufferedReader.readLine();
if (line == null) break;
lineNames.add(line);
}
return lineNames;
} catch (IOException e) {
log.error("读取spi文件时发生异常。。", e);
}
return null;
}
}
3、SpiResolver
用于配置上下文,通过spi的方式加载配置项
public class SpiResolver {
/**
* 通过spi的方式加载配置项
* @param configuration 配置上下文
*/
public void loadFromSpi(Configuration configuration) {
//通过spi文件,获取一个LoadBalance实现
List<ObjectWrapper<LoadBalancer>> loadBalanceWrapper = SpiHandler.getList(LoadBalancer.class);
//将其放入工厂
if (loadBalanceWrapper != null && loadBalanceWrapper.size() > 0){
configuration.setLoadBalancer(loadBalanceWrapper.get(0).getImpl());
}
List<ObjectWrapper<Compressor>> objectWrappers = SpiHandler.getList(Compressor.class);
if (objectWrappers != null) {
objectWrappers.forEach(CompressorFactory::addCompressor);
}
List<ObjectWrapper<Serializer>> serializerObjectWrappers = SpiHandler.getList(Serializer.class);
if (serializerObjectWrappers != null) {
serializerObjectWrappers.forEach(SerializerFactory::addSerializer);
}
}
}