什么是Dubbo SPI
用Dubbo.apache.org官方的话概况就是SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。
那么Dubbo SPI到底是怎么实现的呢?
首先我们来看一下触发SPI的入口
ExtensionLoader.getExtensionLoader(FrameworkExt.class).getSupportedExtensionInstances();
Dubbo SPI机制的前提:接口必须标注@SPI注解。
当dubbo源码中需要获取到FrameworkExt接口的实现类时,会先获取一个ExtensionLoader类。
@SPI
public interface FrameworkExt extends Lifecycle {
}
getSupportedExtensionInstances()则是获取当前接口的所有支持的实现类。
首先,会去ExtensionLoader类对应的缓存中获取类的实例。如果是第一次加载,则加载特定路径下的资源文件。然后根据资源文件中规定的别名和类名加载和实例化接口的实现类。
这边需要注意的是,加载资源文件有多个加载策略。下面附上策略接口的类关系图以及接口源码。

public interface LoadingStrategy extends Prioritized {
String directory();
default boolean preferExtensionClassLoader() {
return false;
}
default String[] excludedPackages() {
return null;
}
/**
* Indicates current {@link LoadingStrategy} supports overriding other lower prioritized instances or not.
*
* @return if supports, return <code>true</code>, or <code>false</code>
* @since 2.7.7
*/
default boolean overridden() {
return false;
}
}
可以看到,LoadingStrategy接口定义了directory方法。通过实现接口实现directory方法来规定每个加载策略加载的路径。
DubboExternalLoadingStrategy: META-INF/dubbo/external/
DubboInternalLoadingStrategy: META-INF/dubbo/internal/
DubboLoadingStrategy: META-INF/dubbo/
ServiceLoadingStrategy: META-INF/services/
举个栗子:
我们当前使用SPI机制去加载FramworkExt所有的实现类,那么程序就会默认通过JDK的类加载器加载上述四个包路径下(META-INF/dubbo/external/、META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/)以org.apache.dubbo.common.context.FrameworkExt为文件名的资源文件。
打开META-INF\dubbo\internal\org.apache.dubbo.common.context.FrameworkExt文件
#别名=类名
config=org.apache.dubbo.config.context.ConfigManager
environment=org.apache.dubbo.common.config.Environment
repository=org.apache.dubbo.rpc.model.ServiceRepository
分别加载并缓存对应的ConfigManager、Environment、ServiceRepository实现类。
不同的类缓存的区别
ExtensionLoader加载的类,缓存的地方是有区别的。
在Dubbo源码中,通过ExtensionLoader加载的类大致上分为四种。
- 标注了@Adaptive的类
- 泛型T的包装类
- 标注了@Activate的类
- 其他的类
标注了@Adaptive的类是缓存在ExtensionLoader类中的Class<?> cachedAdaptiveClass变量下,用volatile进行修饰。
而包装类则是缓存在Set<Class<?>> cachedWrapperClasses变量下,为一个Set集合。
最后标注了@Activate注解的类和其他的类则都缓存在ConcurrentMap<Class<?>, String> cachedNames变量中,其中Map的value为实现类的别名。未设置别名的类则获取类名进行缓存。
思考:标注了@Adaptive和@Activate的类与一般的类的区别
自适应拓展
标注了@Adaptive的类会根据URL配置的不同去动态加载对应的类。
以Protocol接口为例。
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
Protocol接口的export方法标注了@Adaptive,如果是方法上标注了@Adaptive那么会自动生成一个代理类。
下面附上生成的代理类的源码
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}
可以看到自动生成的Protocal$Adaptive类,继承了Protocol接口,并重写了export方法,通过export方法获取当前url配置信息,并调用SPI机制动态的获取对应的协议的处理类。

可激活拓展
标注了@Activate注解的类,可以通过URL配置动态加载使用。
@Activate(group = PROVIDER, value = ACCESS_LOG_KEY)
public class AccessLogFilter implements Filter {
比方说AccessLogFilter类,如果URL配置中配置了accesslog=value,那么该过滤器会被自动加载到过滤器链中,并进行相应的业务处理。
SPI机制在dubbo源码中的应用
SPI机制便于拓展和动态转换接口的实现类。SPI在Dubbo源码中的应用是随处可见的,最常见的就是在Dubbo外部环境初始化的时候,Dubbo会通过SPI机制获取对应的FrameworkExt类的实现类,并完成外部环境初始化步骤,加载配置信息以及相关环境信息。
那么这样就可以在初始化的时候,非常优雅地初始化外部环境。不需要手动一个个调用对应的实现类来进行初始化,也非常的便于管理源代码。
更多的应用就不在这里举例说明了。
SPI流程图
最后附上Dubbo SPI机制的流程图。

本文深入探讨了Dubbo的SPI(Service Provider Interface)机制,解释了其作为服务发现的原理。内容包括SPI的触发入口、加载策略、@Adaptive和@Activate注解的区别以及在Dubbo源码中的应用。此外,还提供了SPI流程图以帮助理解。
2万+

被折叠的 条评论
为什么被折叠?



