Dubbo 的 SPI 机制
参考自:
SPI 是什么
SPI (Service Provider Interface),是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
简单来说就是用来解耦,实现插件的自由插拔。
使用场景
- JDBC驱动加载案例:利用Java的SPI机制,我们可以根据不同的数据库厂商来引入不同的JDBC驱动包;
- SpringBoot的SPI机制:我们可以在
spring.factories
中加上我们自定义的自动配置类,事件监听器或初始化器等; - Dubbo的SPI机制:Dubbo 更是把 SPI 机制应用的淋漓尽致,Dubbo 基本上自身的每个功能点都提供了扩展点,比如提供了集群扩展,路由扩展和负载均衡扩展等差不多接近 30 个扩展点。如果Dubbo的某个内置实现不符合我们的需求,那么我们只要利用其 SPI 机制将我们的实现替换掉Dubbo的实现即可。
Java SPI 介绍
而 Java SPI 是 JDK 内置的一个服务发现机制,它使得接口和具体实现完全解耦。具体的就是你定义了一个接口,然后在 META-INF/services
目录下放置一个与接口同名的文本文件,文件的内容为接口的实现类,多个实现类用换行符分隔。这样就可以通过配置来决定某个接口具体用哪个实现。
最常见的 Java SPI 的使用就是访问数据库时候用到的
java.sql.Driver
接口。比如使用mysql-connector-java-8.0.17
的模块时,会在META-INF/services
目录下有java.sql.Driver
文件,文件内容只有一行com.mysql.cj.jdbc.Driver
。就是指定java.sql.Driver
接口的实现类为com.mysql.cj.jdbc.Driver
。 Java SPI 的使用:
先在
META-INF/services
目录下,创建以接口的全限定名为文件名的文件,然后每行的内容为接口实现类的全限定名。// xxx 指的是接口类 ServiceLoader<xxx> serviceLoader = ServiceLoader.load(xxx.class); Iterator<xxx> iterator = serviceLoader.iterator(); while(iterator.hasNext()) { xxx xx = iterator.next(); }
Java SPI 的局限性
Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,而无法按需加载实现类,可能会造成资源的浪费。
Spring SPI 介绍
Spring 的功能和JDK的相差无几,最大的区别是所有扩展点写在一个 spring.factories 文件中
Spring 的 SPI 配置文件是一个固定的文件 - META-INF/spring.factories
,功能上和 JDK 的类似,每个接口可以有多个扩展实现,使用起来非常简单:
//获取所有factories文件中配置的LoggingSystemFactory
List<LoggingSystemFactory>> factories =
SpringFactoriesLoader.loadFactories(LoggingSystemFactory.class, classLoader);
Spring 的 SPI 主要用在 Spring Boot 中,而 Spring Boot 中的 ClassLoader 会优先加载项目中的文件,而不是依赖包中的文件。所以如果在你的项目中定义个 spring.factories 文件,那么你项目中的文件会被第一个加载,得到的 Factories 中,项目中 spring.factories 里配置的那个实现类也会排在第一个
如果我们要扩展某个接口的话,只需要在你的项目(spring boot)里新建一个META-INF/spring.factories
文件,只添加你要的那个配置,不要完整的复制一遍 Spring Boot 的 spring.factories 文件然后修改
比如我只想添加一个新的 LoggingSystemFactory 实现,那么我只需要新建一个META-INF/spring.factories
文件
Spring SPI,不能像 Dubbo SPI 那样,获取指定的实现类,它只能按顺序获取所有实现。
Spring Boot 中 spring.factories 的配置如下:
# Logging Systems
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# ConfigData Location Resolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\
org.springframework.boot.context.config.StandardConfigDataLocationResolver
......
Dubbo SPI 介绍
而 Dubbo 就自己实现了一个 SPI,拥有按需加载的功能,具体的使用是给实现类加个“名子”,通过这个名字去文件里面找到对应的实现类全限定名然后加载实例化即可。
failover=com.alibaba.dubbo.rpc.cluster.support.FailoverCluster
failfast=com.alibaba.dubbo.rpc.cluster.support.FailfastCluster
failsafe=com.alibaba.dubbo.rpc.cluster.support.FailsafeCluster
failback=com.alibaba.dubbo.rpc.cluster.support.FailbackCluster
folking=com.alibaba.dubbo.rpc.cluster.support.FolkingCluster
available=com.alibaba.dubbo.rpc.cluster.support.AvailableCluster
mergeable=com.alibaba.dubbo.rpc.cluster.support.MergeableCluster
broadcast=com.alibaba.dubbo.rpc.cluster.support.BroadcastCluster
并且 Dubbo SPI 除了可以按需加载实现类之外,增加了 IOC 和 AOP 的特性,还有个自适应扩展机制。
Dubbo 依靠 SPI 机制实现了插件化功能,几乎将所有的功能组件做成基于 SPI 实现,并且默认提供了很多可以直接使用的扩展点,实现了面向功能进行拆分的对扩展开放的架构。
Dubbo 对配置文件目录的约定,不同于 Java SPI ,Dubbo 分为了三类目录。
- META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI 。
- META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件。
- META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件。
// 通过 Dubbo SPI 获取接口的实现类
xxx xx = ExtensionLoader.getExtensionLoader(xxx.class).getExtension("实现类对应的名字");
ExtensionLoader 是类似 Java SPI 中 ServiceLoader 的存在,负责实现类的加载。
大致流程就是先通过接口类找到一个 ExtensionLoader ,然后再通过 ExtensionLoader.getExtension(name) 得到指定名字的实现类实例。
Dubbo SPI 的源码分析 (2.6.5 version)
getExtensionLoader() 获取拓展类加载器
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// 省略 type 为空的抛错代码
// 省略 type 不是接口的抛错代码
// 省略 type 没有标注 @SPI 注解的抛错代码
// 从缓存中获取
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
// 如果缓存中没有的话,则新建一个,并加入缓存中
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
getExtension() 获取拓展类
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
// 获取默认的拓展类
return getDefaultExtension();
}
// 从缓存中获取,并持有对象
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
// 如果缓存中没有的话,就新建一个,并加入缓存中
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
// 双重锁检查
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 重点:获取