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. react组件父传子

    react组件父传子,子组件使用父组件的数据,用props import React, { Component } from 'react'; class App extends Component ...

  2. Basic Router Architecture

    from the book principles and practices of interconnection networks  the chapter router architecture ...

  3. Mybatis在oracle数据库中插入数据后返回自增值ID

    1.将id设置成自增序列 CREATE OR REPLACE TRIGGER "DATALIB"."TRIG_USER_ADD" BEFORE INSERT O ...

  4. maven的传递性依赖

    一.概念: 假如有maven项目A,项目A依赖项目B,项目B依赖项目C,我们说A对B是第一直接依赖,B对C是第二直接依赖,那么他们的依赖关系:A---->B----->C,那么我们执行项目 ...

  5. Win10通知区域图标设置;windows10系统图标合并;Windows10系统通知合并

    1.一直喜欢Windows7的通知图标合并 2.通过查阅找到方法 a.输入命令语句   win+R  :shell:::{05d7b0f4-2121-4eff-bf6b-ed3f69b894d9} 回 ...

  6. typecho 调用评论最多热门文章

    在当前主题的functions.php文件中添加以下函数代码: function getHotComments($limit = 10){ $db = Typecho_Db::get(); $resu ...

  7. html的那些小小细节

    1.get post方式提交的不同 get:数据放在url的后面,用?连接                        会在客户端保留缓存信息,不安全                        ...

  8. Mysql之数据表操作

    数据表操作: 查看当前数据库中所有的表: show tables; 查看当前使用数据库: select database(); 使用数据表: use 表名; 创建数据表: create table 数 ...

  9. noip2017d1t1

    我们知道因为a,b互质,ax+by=n若存在一组解(x0,y0),则(x0+kb,y0-ka)也是一组解,而我们要保证有正整数解的情况下n最大,我们不妨将x0设为最大的负整数-1,考虑最大的y0能为多 ...

  10. POJ2270&&Hdu1808 Halloween treats 2017-06-29 14:29 40人阅读 评论(0) 收藏

    Halloween treats Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 8565   Accepted: 3111 ...