Dubbo SPI 是Dubbo用于支持 扩展 一个功能。
关于 SPI
的具体意思以及内容,相信 官方这篇文档已经讲的足够通俗易懂了:
http://dubbo.apache.org/zh-cn/blog/introduction-to-dubbo-spi.html
里面提到了两点对于SPI 功能的解释,有两点:
Dubbo 支持多种协议,多种配置中心,注册中心,传输层协议的配置,当然里面也有相关代码维护,如果以设计模式角度来说,当然可以通过工厂模式来完成多协议的配置。
上文中对于 SPI 的优点,从框架维护者到使用者都有涉及,不多废话,听博主一层一层解析SPI 过程吧
主要从 ReferenceConfig
入手,它里面有 声明具有 SPI 声明的多个接口,这里以 Protocol
为例:
private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
接下来看看 Protocol
的声明:
/**
* Protocol. (API/SPI, Singleton, ThreadSafe)
*/
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
Exporter export(Invoker invoker) throws RpcException;
@Adaptive
Invoker refer(Class type, URL url) throws RpcException;
void destroy();
}
Protocol
使用了 @SPI
修饰,并且有了默认值是 dubbo
,同时定义了 export
,refer
等方法。
进去看看 getExtensionLoader
方法:
public static ExtensionLoader getExtensionLoader(Class type) {
// 检查下type是否为null
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
// 检查是否为接口
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
// 检查是否有 @SPI 注解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
// 尝试从缓存中去加载
ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
}
return loader;
}
前面有对 type
进行 判断,包括:
最后尝试去 EXTENSION_LOADERS
获取,如果没有的化,则会new一个ExtensionLoader
放入,
最后也同样是 返回该 ExtensionLoader
。
private ExtensionLoader(Class> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
首先会记录该 type
,作为 SPI 的类型,而后会去初始化 objectFactory
, 如果当前type是 ExtensionFactory
,则初始化为null,否则继续调用 ExtensionLoader
的 getExtensionLoader
去获取 ExtensionFactory
这个 SPI 实例。
这是一个递归调用,所以下一个先回调用 getExtensionLoader
,而后会给 ExtensionFactory 也分配一个 ExtensionLoader
,而后执行完之后,则会调用 ExtentionLoader
的 getAdaptiveExtension
。
这里以传入的 type 为 ExtensionFactory
的 getAdaptiveExtension
:
public T getAdaptiveExtension() {
// 看是否已经初始化了
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
// 再尝试获取一次
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
// 初始化
instance = createAdaptiveExtension();
// 放入 cachedAdaptiveInstance 中
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
// 报错了,放入了 createAdaptiveInstanceError
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
// 说明出错了
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
// 返回instance
return (T) instance;
}
判断是 当前缓存的 cachedAdaptiveInstance
优先实例 为 空,如果为空,则需要调用 createAdaptiveExtension
创建一个。
在 createAdaptiveExtension
中,则主要会调用 getAdaptiveExtensionClass
获取优先加载的类,并使用 newInstance
new出实例。
而后则会调用 injectExtension
对newInstance
出来的实例进行 SPI
属性注入。
private Class> getAdaptiveExtensionClass() {
// 加载类
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 如果加载的类中,没有 @Adaptive类型,则需要创建一个默认的 @Adaptive字节码类。
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
首先会调用 getExtensionClass
去从 文件目录下读取类,而后将 有 @Adaptive
的进行初始化。
getExtensionClass
则会先判断 该类型的 SPI
类是否已经加载,即从 cachedClasses
中读取。否则将会调用 loadExtensionClasses
去调用。
private Map> getExtensionClasses() {
Map> classes = cachedClasses.get();
// 判断是否已经缓存
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 否则将会去获取load 以下对应文件中内容
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
loadExtensionClasses 则是从不同位置文件夹去加载配置,由于 加入旧版本的支持,所以会 将根目录下 com.alibaba
对应包名也处理下:
private Map> loadExtensionClasses() {
cacheDefaultExtensionName();
Map> extensionClasses = new HashMap<>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
在 cacheDefaulltExtensionName
方法中,主要目的就是解析 @SPI注解,获取 value值,然后将 value值 存储到 当前ExtensionLoader 的cachedDefaultName
中。
接下来执行了多次 loadDirectory
,主要就是加载 META-INF 下面 类名相同的扩展文件,主要就是 dubbo 的SPI机制。
主要有以下几个方面:
META-INF/dubbo/internal/
META-INF/dubbo/
META-INF/services/
另外,对于旧版本的 SPI问题,Dubbo也给出了解决方案,使用 type.getName().replace("org.apache", "com.alibaba")
将 org.apache
改为com.alibaba
通过给定的 ExtensionLoader中的type,从而加载对应名字的 SPI 文件。
从而反射将其初始化。
private void loadDirectory(Map> extensionClasses, String dir, String type) {
String fileName = dir + type;
try {
Enumeration urls;
// 获取类加载器
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
// url 是文件的地址,这一步只是加载对应 spi 文件名 的所有的文件地址
urls = classLoader.getResources(fileName);
} else {
// 直接加载系统资源
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
由于 spi 文件的 格式是key=value
,所以需要读取这一段并加载里面所得到的类名:
另外,由于 在loadResource时候,有些 可插拔式的接口并没有完全引入相应的包,例如在加载 Protocol
的SPI时候,初始化
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
,如果没有引入 hessian
相应依赖,则会报错,放入 exceptions
中。
private void loadResource(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
// 读取一行,# 标识注释
final int ci = line.indexOf('#');
if (ci >= 0) {
line = line.substring(0, ci);
}
// 过滤空格
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
// 加载类。
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
// 允许报错,因为dubbo 的spi里面定义了 很多协议,但是需要有对应jar包引入,如果没有引入,则在这一步,则会报错,例如hessian 和 http。webservice等
// 这一步错误,主要是在 Class.forName 这一步出错。
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
这个方法挺重要的,在初始化对应 SPI 中类时候,需要对其类进行区分:
private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class> clazz, String name) throws NoSuchMethodException {
// 检查是否有几成关系
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 缓存clazz,到 cachedAdaptiveClass 中
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
// 包装类,就是这个类有一个构造方法,然后只有一个参数就是type
cacheWrapperClass(clazz);
} else {
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 缓存 Activate的class
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 缓存名字,可以多个名字对应同一个类 到
cacheName(clazz, n);
// 缓存类
saveInExtensionClass(extensionClasses, clazz, name);
}
}
}
}
最后执行 saveInExtensionClass
,将 对应 SPI 扩展类,放到 extensionClass中,依次返回给loadResource
、loadDirectory
、loadExtensionClasses
,最终设置给 cachedClasses
。
总结以下,整个 SPI 加载过程
ExtensionLoader.getExtensionLoader
去初始化或者获取已有的 ExtensionLoader
getExtensionLoader
中会先去检查该type
是否已经加载,有则直接返回,否则new 一个 ExtensionLoader
返回getAdaptiveExtension
主要获取使用 @Adaptive
修饰的 扩展,可以理解为默认扩展DUBBO_INTERNAL_DIRECTORY
,DUBBO_DIRECTORY
,SERVICES_DIRECTORY
去加载对应文件名为 type
的SPI 类。ExtensionLoader
的多个字段缓存中,具体可以看以下表格ExtensionLoader 是 SPI 功能的一个核心的类,这里单独提出来,对其机制,以及 字段进行单独分析,可以先读完全篇文章在读这里
字段:
字段名 | 类型 | 作用 |
---|---|---|
EXTENSION_LOADERS | ConcurrentMap |
用于存储 该 type 的 SPI 所对应的 ExtensionLoader 如果有则说明该类型的 SPI 已经Load 过 |
objectFactory | ExtensionFactory |
ExtensionFactory 也是一个SPI类,主要用于加载 SPI类,主要有 SpiExtensionFactory 和 SpringExtensionFactory 和 AdaptiveExtensionFactory |
cachedAdaptiveInstance | Holder |
用于保存当前的 该 SPI 的 优先缓存实例 |
cachedWrapperClasses | Set |
用于存储该 SPI 类对应的 Wrapper 类,即包装类,包装类主要作用可以使Filter或者修饰作用,其特点是 有一个单参数的构造方法,且其参数为 该 SPI 类 |
cachedNames | ConcurrentMap |
将 SPI 文件中,以Class 为key, name为value |
cachedActivates | Map |
用于存储 被 @Active 注解修饰的类和映射,名字是 SPI 对应的key,Object是 Activate 对象 |
如果整个扩展子类都没有使用 @Adaptive
修饰怎么办?此时 使用的是 @SPI
中的默认扩展名字。并且 Dubbo 会生成一个默认的代理,通过SPI
中 默认扩展名字获取具体的 扩展类。
而后使用createAdaptiveExtensionClass
生成代理类,欲知下文如何,且看下篇分析
觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,Dubbo小吃街不迷路: