dubbo SPI

SPI,全程Service Provider interface, java中的一种借口扩展机制,将借口的实现类注明在配置文件中,程序在运行时通过扫描这些配置文件从而获取全部的实现类。

java 原生的spi将借口的实现类信息放在META-INF/services/<借口全限定名>文件中。spi被广泛应用于各种框架及中间件中,用以提升框架的扩展性。

dubbo也使用spi来提供各种扩展,但是dubbo并未使用java原生的spi,而是实现了自己的spi机制。dubbo的spi实现ExtensionLoader相较于java自带的ServiceLoader还是有不少改进的,例如为每个实现类赋一个名称,以k-v存储在配置文件中,并且在代码中也可以通过名称获取到相应的实现类,另外,ExtensionLoader的加载目录也比ServiceLoader更多,

ExtensionLoader.getExtensionLoader

这个方法是入口

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
//做一些非空检查
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
//必须是接口,spi是针对接口的扩展机制
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() + "!");
} //EXTENSION_LOADERS是ExtensionLoader的全局缓存,interface->ExtensionLoader
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;
}

ExtensionLoader.getExtension(String name)

获取一个ExtensionLoader实例后,通过该方法获取一个扩展类的实例

private T createExtension(String name) {
//核心逻辑。找到并加载所有的扩展类
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//EXTENSION_INSTANCES是扩展类实例的全局缓存
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//自动注入一些属性,实现IOC特性
injectExtension(instance);
//对原始实例进行包装,实现AOP特性
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
//层层包装
//这里有个问题,如果包装类有多个,那么他们的顺序如何???
//spring中对于多个通知类Advice的情况是会进行排序的
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}

getExtensionClasses

//加载该接口所有的扩展类,这个方法主要通过双检查锁机制实现了缓存逻辑,

private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//核心逻辑
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}

loadExtensionClasses

//这个方法返回的类中不包括带有Adaptive注解的类,以及包装类
private Map<String, Class<?>> loadExtensionClasses() {
//缓存默认扩展类的名称
//默认扩展类名称通过接口上的SPI注解获取,就是SPI注解的value
cacheDefaultExtensionName(); //将加载的扩展类实例放到该Map中,
Map<String, Class<?>> extensionClasses = new HashMap<>();
//加载 META-INF/dubbo/internal/目录下的配置文件
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
//加载 META-INF/dubbo/internal/目录下alibaba自己的相关接口,通过将org.apache替换为com.alibaba
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//加载 META-INF/dubbo/目录下的配置文件
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
//同上
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
//加载 META-INF/services/目录下的配置文件
//这里需要注意的是,由于项目中可能会引入使用jdk ServiceLoader的包,
// 那么 META-INF/services/目录下可能存在ServiceLoader的配置文件,而这些文件中存储的实现类并不是以key-value形式存储的
// 这样ExtensionLoader在加载的时候就找不到name,这个后续会进行一些处理
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
//同上
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}

可以看出加载了META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/三个目录下的配置文件,除了加载原接口相关的配置文件,ExtensionLoader额外加载了alibaba自己的相关接口的扩展类。接下来,我们看一下loadDirectory方法。

loadDirectory

//从相关的目录找到对应接口类型的配置文件,并加载全部扩展类
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
//配置文件全路径:文件夹+接口名称,META
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls;
//首先获取ClassLoader, 获取顺序是:线程上下文类加载器->加载ExtensionLoader的类加载器->系统类加载器(AppClassLoader)
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
//获取资源文件的url
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);
}
}

loadResource

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
//配置文件必须是utf-8编码格式
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;
//文件中每一行以:name=extension.class.name格式存储扩展类,
//以=号作为分隔符,就是properties文件格式
int i = line.indexOf('=');
//这里少考虑了i==0的情况????
//name可以是空,也就是说可以只有扩展类名,而没有name,
//实际上这种格式就是ServiceLoader的资源文件,
// 对于name为空的情况的处理逻辑在loadClass方法中
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
//重点关注一下name为空的处理逻辑
//这里调用Class.forName加载实现类
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
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);
}
}

这里少考虑了i==0的情况????

loadClass

//加载一个扩展类
private void loadClass(Map<String, Class<?>> 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.");
}
//设置adaptive, adaptive只能有一个,如果有多个扩展类上都有Adaptive注解,那么会抛异常
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
//保存包装类,
//判断包装类就是看这个类有没有只有一个参数的构造器,而且参数的类型必须是对应的扩展接口类型
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
//代码进入这个分支,说明该类是普通的扩展类
//必须要有无参构造器
clazz.getConstructor();
//前面也讲过,name是可以为空的,资源文件中可以只有实现类的类名称
if (StringUtils.isEmpty(name)) {
//通过Extension注解找name的值,
//如果没有Extension注解,那么通过类名称获取name值,具体处理方法不细说
name = findAnnotationName(clazz);
if (name.length() == 0) {
//Extension注解中的value为空字符串,这种情况抛异常
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
} //可以有多个别名,类似spring中bean的别名,多个名称以逗号分隔
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
//如果类带有Activate注解,那么将其缓存下来
cacheActivateClass(clazz, names[0]);
//将
for (String n : names) {
//将名字缓存下来,只缓存第一个名字
cacheName(clazz, n);
//将加载的类放到一路传进来的extensionClasses中,
//如果有多个别名,每个别名都存储
saveInExtensionClass(extensionClasses, clazz, name);
}
}
}
}

至此,就查找到了该接口的全部扩展类。

实现IOC特性

前面讲到,在createExtension方法中,创建完实例后,会调用injectExtension方法自动组注入一些属性,

injectExtension

 private T injectExtension(T instance) {
try {
//objectFactory是AdaptiveExtensionFactory的一个实例
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (isSetter(method)) {
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}

我们首先看一下objectFactory成员是怎么来的

private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

对于一般的接口类型,objectFactory成员就是带Adaptive注解的ExtensionFactory接口的实现类,其实就是AdaptiveExtensionFactory的一个实例。

AdaptiveExtensionFactory

//该类与dubbo spi的自适应机制相关
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory { private final List<ExtensionFactory> factories; public AdaptiveExtensionFactory() {
//加载ExtensionFactory接口的所有实现类
//并缓存到factories中
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
//这里思考:为什么AdaptiveExtensionFactory不会循环调用构造器???
//原因在ExtensionLoader.loadClass方法中,Adaptive注解的类和包装类都只缓存下来,不在正常的查找扩展类的范围内,
//getSupportedExtensions实际上返回的是cachedClasses成员保存的类,是不包括Adaptive注解的类和包装类的
//所以这里才不会发生循环调用AdaptiveExtensionFactory的构造器
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
} @Override
public <T> T getExtension(Class<T> type, String name) {
//遍历所有ExtensionFactory实现类,
//返回第一个不是null的值
//我们需要看一下都有哪些ExtensionFactory实现类
//注意,这里思考一下,AdaptiveExtensionFactory为什么不会循环调用???
//这里仍然涉及到ExtensionFactory多个实现类排序的问题???
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}

}

这里仍然涉及到ExtensionFactory多个实现类排序的问题???

这里通过遍历所有的ExtensionFactory实现类,找到相应的属性值。ExtensionFactory实现类有MyExtensionFactory,SpiExtensionFactory,AdaptiveExtensionFactory,SpringExtensionFactory,除去AdaptiveExtensionFactory与自适应机制相关,不起真正的依赖寻找的作用;SpiExtensionFactory是针对带有SPI注解的类型进行自动注入依赖;

而对与一般的类型,则是使用SpringExtensionFactory来进行依赖注入,在spring的BeanFactory中查找匹配的Bean实例,大概逻辑是:先通过beanName来查找,找不到再通过类型来查找。

实现AOP特性

dubbo的aop的实现略显简单,使用静态代理模式,代理类由用户实现,可以通过多层包装实现多级拦截。

如果有多个包装类的情况下,同样存在顺序的问题, ExtensionLoader有好多地方应该确定类的调用顺序,却没相应的排序规则,甚至都没有预留出排序接口,这点spring做得非常好,

spring中凡是涉及到多个平级类的链式调用或遍历查找的,都会实现Ordered接口或PriorityOrdered接口,包括aop中有多个通知类Advice的情况,都会以一定的规则进行排序。

dubbo源码阅读之SPI的更多相关文章

  1. 【Dubbo源码阅读系列】之远程服务调用(上)

    今天打算来讲一讲 Dubbo 服务远程调用.笔者在开始看 Dubbo 远程服务相关源码的时候,看的有点迷糊.后来慢慢明白 Dubbo 远程服务的调用的本质就是动态代理模式的一种实现.本地消费者无须知道 ...

  2. 【Dubbo源码阅读系列】服务暴露之远程暴露

    引言 什么叫 远程暴露 ?试着想象着这么一种场景:假设我们新增了一台服务器 A,专门用于发送短信提示给指定用户.那么问题来了,我们的 Message 服务上线之后,应该如何告知调用方服务器,服务器 A ...

  3. 【Dubbo源码阅读系列】服务暴露之本地暴露

    在上一篇文章中我们介绍 Dubbo 自定义标签解析相关内容,其中我们自定义的 XML 标签 <dubbo:service /> 会被解析为 ServiceBean 对象(传送门:Dubbo ...

  4. 【Dubbo源码阅读系列】之 Dubbo SPI 机制

    最近抽空开始了 Dubbo 源码的阅读之旅,希望可以通过写文章的方式记录和分享自己对 Dubbo 的理解.如果在本文出现一些纰漏或者错误之处,也希望大家不吝指出. Dubbo SPI 介绍 Java ...

  5. Dubbo源码阅读顺序

    转载: https://blog.csdn.net/heroqiang/article/details/85340958 Dubbo源码解析之配置解析篇,主要内容是<dubbo:service/ ...

  6. Dubbo源码分析之 SPI(一)

    一.概述 dubbo SPI 在dubbo的作用是基础性的,要想分析研究dubbo的实现原理.dubbo源码,都绕不过 dubbo SPI,掌握dubbo SPI 是征服dubbo的必经之路. 本篇文 ...

  7. Dubbo源码解析之SPI(一):扩展类的加载过程

    Dubbo是一款开源的.高性能且轻量级的Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用.智能容错和负载均衡,以及服务自动注册和发现. Dubbo最早是阿里公司内部的RPC框架,于 ...

  8. dubbo源码分析6——SPI机制中的AOP

    在 ExtensionLoader 类的loadFile方法中有下图的这段代码: 类如现在这个ExtensionLoader中的type 是Protocol.class,也就是SPI接口的实现类中Xx ...

  9. dubbo源码分析5——SPI机制_AdaptiveExtension的原理和作用

    private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().n ...

随机推荐

  1. vue属性值调方法

    <td>{{showPrice(product.sellprice)}}</td>

  2. Le Chapitre VII

    Le cinquième jour, toujours grâce au mouton, ce secrèt de la vie du petit prince me fut révélé. Il m ...

  3. awk 用法(转)

    原文:https://www.cnblogs.com/xudong-bupt/p/3721210.html awk是行处理器: 相比较屏幕处理的优点,在处理庞大文件时不会出现内存溢出或是处理缓慢的问题 ...

  4. 实现两个sym转一个sym

    CVO输出如果是一个像素并行输出,选择内嵌人插入同步码.如果两个像素并行输出是不能选择内嵌的,只能选择分离的方式.如果把输出的并行数据给VIP并且要求是内嵌,那只能在内部转或者外部转. 这里是实现外部 ...

  5. asp.net上传文件夹

    最近公司做工程项目,实现文件夹上传. 网上找了一天,发现网上很多代码都存在相似问题,最后终于找到了一个符合要求的项目. 工程如下: 这里对项目的文件夹上传进行分析,实现文件夹上传,如何进行文件夹上传. ...

  6. 2018-03-13 HTTP Socket TCP学习

    协议学习: https://www.jianshu.com/p/a5410f895d6b https://www.jianshu.com/p/42260a2575f8 实际例子: nano实际例子,和 ...

  7. 把sublime text打造成python交互终端(windows和Ubuntu)

    作者:tongqingliu 转载请注明出处:http://www.cnblogs.com/liutongqing/p/7015958.html 把sublime text打造成python交互终端 ...

  8. (DP 雷格码)Gray code -- hdu -- 5375

    http://acm.hdu.edu.cn/showproblem.php?pid=5375 Gray code Time Limit: 2000/1000 MS (Java/Others)    M ...

  9. 2.Java面向对象编程三大特性之继承

    在<Think in Java>中有这样一句话:复用代码是java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复用代码并对其加以改变是不够的,他还必须能够做更多的事情.复用 ...

  10. MapReduce_架构

    架构 MapReduce1.x JobTracker:JT(作业管理者) 将作业分解成一堆的任务:Task(MapTask和ReduceTask) 将任务分派给TaskTracker运行 作业的监控. ...