0%

Dubbo源码 SPI实现之ExtensionLoader

Dubbo SPI

SPI 全称为 (Service Provider Interface) ,JDK也默认提供了SPI的一种实现,不过对比Dubbo的实现,JDK的实现就非常简单。

简单说下JDK默认的SPI用法。

  1. 定义Service接口
  2. 增加Service实现类
  3. META-INF/services目录下建立以接口包全名命名的文件,文件中写入实现类的包名+类名
  4. 用Java提供的ServiceLoader来加载实现 工程结构示例

看下如何使用ServiceLoader进行加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* com.cxd.spi.SayHello文件内容如下:
* com.cxd.spi.impl.TomSayHello
* com.cxd.spi.impl.JerrySayHello
**/
public class SPIMain {
public static void main(String[] args) {
ServiceLoader<SayHello> sayHellos = ServiceLoader.load(SayHello.class);
Iterator<SayHello> sayHelloIterator = sayHellos.iterator();
while (sayHelloIterator.hasNext()) {
SayHello sayHello = sayHelloIterator.next();
sayHello.say();
}
}
}

ServiceLoader的实现也是比较简单,说下过程,不再列出源码。ServiceLoader根据传入的class去约定的目录下找到相应的文件逐行读取,然后用当前线程的ClassLoader加载文件中定义的实现类,通过class.newInstance()创建实例对象。ServiceLoader通过返回迭代器的方式让我们遍历所有的接口实现。

Dubbo整体思路也是如此,但是Dubbo SPI实现的功能就比较强大了。
SPI通过定义文件实现,但是文件格式并没有局限,Dubbo中使用key=value的形式来进行定义,这也是dubbo能够灵活支持多种协议以及实现良好扩展性的关键所在。

在Dubbo里面使用ExtensionLoader来加载扩展点(即:接口的具体实现),每类接口Dubbo都有默认的实现,当然我们也可以根据自己的业务需要来定义自己的扩展,而扩展的方式也非常简单,SPI的方式给Dubbo提供了框架极好的扩展性。

下面我们主要来看ExtensionLoader,它是Dubbo对SPI实现的核心,也是Dubbo的核心。

  1. 每个定义的SPI的接口都会构建一个ExtensionLoader实例,ExtensionLoader采用工厂模式,以静态方法向外提供ExtensionLoader的实例。实例存储在ExtensionLoader内部静态不可变变量中。
    1
    2
    //ExtensionLoader.java
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
  2. 外部使用时调用:ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();getExtensionLoader方法创建ExtensionLoader实例,getAdaptiveExtension方法会加载扩展点中的实现类,并创建或者选择适配器。
  • 读取SPI注解的value值,如果value不为空则作为缺省的扩展点名
  • 依次读取指定路径下的扩展点
    META-INF/dubbo/internal/
    META-INF/dubbo/
    META-INF/dubbo/services/

getAdaptiveExtension方法最终调用loadFile来完成对SPI文件的读取解析。

  1. loadFile方法逐行读取SPI文件内容并进行解析
  • 实现类上是否含有@Adaptive注解,如果有,则将其作为适配器缓存到cachedAdaptiveClass,并进入下一行配置的解析,一个SPI只能有一个适配器,否则会报错;
  • 如果实现类上没有@Adaptive注解,那么看其是否存在以当前获取接口类型为入参的构造器,如果有,则将其作为包装器(wrapper)存入cachedWrapperClasses变量;
  • 如果实现类既没有@Adaptive注解,也不是包装器,那它就是扩展点的具体实现
  • 判断扩展实现上是否有@Activate注解,如果有,将其缓存到cachedActivates(一个类型为Map的变量)中,然后将其key作为扩展点的名字,放入cachedClasses(一个类型为Holder>>的变量)中,dubbo支持在配置文件中n:1的配置方式,即:不同名的协议使用同一个SPI实现,只要配置名字按照正则\s*[,]+\s*命名即可。
  1. 完成对文件的解析后,getAdaptiveExtension方法开始创建适配器实例。如果cachedAdaptiveClass已经在解析文件中确定,则实例化该对象;如果没有,则创建设配类字节码。

为什么要用适配器?一个接口有多种实现,SPI机制也是如此,这是策略模式。Dubbo采用统一数据模式com.alibaba.dubbo.common.URL(dubbo自定义数据结构),它贯穿系统的整个执行过程,URL中定义的协议类型字段protocol,会根据具体业务设置不同的协议。url.getProtocol()值可以是dubbo、webservice、zookeeper或者redis,当然也可以是其他类型。适配器的作用是根据url.getProtocol()的extName,去选取解析该协议的策略。

Dubbo能够为没有适配器的SPI生成适配器字节码的必要条件:

  • 接口方法中必须至少有一个方法打上了@Adaptive注解
  • 打上了@Adaptive注解的方法参数必须有URL类型参数或者有参数中存在getURL()方法

Dubbo生成的适配器代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}

public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}

public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}

public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
if (arg0 == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
}

Dubbo生成代码后需要对代码进行编译,大家注意,Dubbo中服务皆是SPI,编译器的获取依然需要ExtensionLoader来加载,Dubbo缺省编译器为javassist。Dubbo在加载Compiler时,Compiler的实现类之一AdaptiveCompiler中有@Adaptive的注解,即有已实现的适配器,Dubbo不必为Compiler生成字节码,不然此时就死循环了。

拿到适配器Class后,Dubbo对适配器进行实例化,并且实现了一个类似IOC功能的变量注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
//找出适配器中公开的set方法
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class<?> pt = method.getParameterTypes()[0];
try {
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
//从工厂中获取变量的值
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
//反射调用set方法设置变量值
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}

IOC代码非常简单,关键点在objectFactory上,objectFactory是个什么东东?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

/**
* ExtensionFactory默认扩展点
* spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory
* adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
* spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
* 其中adaptive被标记为@Adaptive,不会出现在factories中
*/
private final List<ExtensionFactory> factories;

public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
//loader.getSupportedExtensions()返回自然排序的name集合,所以spi->spring
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}

public <T> T getExtension(Class<T> type, String name) {
//因为list中spi在前,所以先从SpiExtensionFactory中查找,如果未找到再去SpringExtensionFactory中找
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}

}

小生不才,以上如有描述有误的地方还望各位不吝赐教 !^_^!