JDK SPI 机制就存在以下一些问题:
- 实现类会被全部遍历并且实例化,假如我们只需要使用其中的一个实现,这在实现类很多的情况下无疑是对机器资源巨大的浪费。
- 无法按需获取实现类,不够灵活,我们需要遍历一遍所有实现类才能找到指定实现。
Dubbo SPI 以 JDK SPI 为参考做出了改进设计,进行了性能优化【kv缓存】以及功能增强,Dubbo SPI 机制的出现解决了上述问题。除此之外,Dubbo 的 SPI 还支持自适应扩展以及 IOC 和 AOP 等高级特性。
//扩展接口类型
private final Class<?> type;
//存储了扩展实现类和扩展名的关系
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
//存储了扩展名和扩展实现类之间的关系
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
//存储了扩展实现类类名与实例对象之间的关系
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
//扩展接口对@SPI注解配置的value
private String cachedDefaultName;
Dubbo SPI 的配置做出了改进,在 Dubbo 中有三种不同的目录可以存放 SPI 配置,用途也不同,优先级由低到高如下所示:
- META-INF/services/ 目录:此目录配置文件用于兼容 JDK SPI 【ServicesLoadingStrategy】。
- META-INF/dubbo/ 目录:此目录用于存放用户自定义 SPI 配置文件【DubboLoadingStrategy】。
- META-INF/dubbo/internal/ 目录:此目录用于存放 Dubbo 内部使用的 SPI 配置文件【DubboInternalLoadingStrategy】。
其中,只有ServicesLoadingStrategy在其对应目录文件中是不支持kv形式的。
public class ExtensionLoader<T> {// 利用JDK Spi机制提前初始化 LoadingStrategy的三个实现类private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
}
@SPI注解可以指定生效候选类的扩展名,否则扩展名选值为类名。候选类可以通过注解@Extension指定当前候选类的扩展名。
策略ServicesLoadingStrategy最终利用类加载器加载候选类资源的URL,得到候选类的二进制流,逐行读取,…,解析…返回。