Dubbo源码分析–基于SPI的可扩展框架
dubbo是阿里巴巴开源出来的一套分布式服务框架,该框架可以比较方便的实现分布式服务的开发,调用等,该框架的教程地址为
http://dubbo.io/Home-zh.htm
代码已经托管到github上。
正好项目里使用了一套网关的框架来做分布式服务开发,该网关的框架是在dubbo的基础上改造而来的,改dubbo默认的基于netty的分布式服务调用为将请求写入到redis队列中,而在处理方则订阅对应的队列,处理完成后,再将结果写回到redis中,从而返回给客户端,由于使用了redis来作为队列,因此可以起到一定的缓冲的作用,起到一定程度上的削峰填谷。
由于该框架是在dubbo的基础上开发的,利用闲暇的时间下载了dubbo的源码大致的翻了一遍,结合dubbo的用户手册,看了一遍看得不明所以;正好最近处于项目的间隙期,因此仔细的研读了一遍代码,感觉收获颇多。。下面简单的记录下自己关于SPI方面的理解。
dubbo的SPI机制是在标准的jdk的SPI的机制上扩展加强而来的
SPI的实现在dubbo中由以下几个annotation来实现。
1. SPI 注解,,使用SPI注解来标识一个扩展点,该注解一般是打在接口上的,DUBBO的扩展点都是基于接口的。
2. Adaptive注解 该注解主要作用在方法上,使用该注解可以根据方法的参数值来调用的具体的实现类的对应方法
3. Activate 注解,该注解一般作用在实现类上,使用该注解一般是对于Filter类型的类,来决定是该类是否加入到Filter的执行器责任链中
SPI机制实现的核心类
ExtensionLoader, 该类是SPI机制实现的核心类,该类提供了以下静态方法getExtensionLoader(Class type),该方法返回了加载此class的ExtensionLoader, 通过该ExtensionLoader来创建对应的type的类的实例,
该类还提供了以下方法来获取对应的加载类的实例
1. getAdaptiveExtension(String name)
通过该方法来获取该type的具有adaptive过的扩展实现,也即是调用返回的实例的方法的时候,如果方法上有Adaptive注解,则会在方法调用的时候才会根据方法的参数调用到对应的实例,
该方法一般用在一些高层次的代理实现中,如ReferenceConfig中包含有如下定义
private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Protocol有很多个实现类,但是ReferenceConfig作为服务提供方的一个接口实例,此时并不知道该调用哪个实现类,当生成一个refprotocol来代表所有的实现类,当调用到该类中的export方法的时候,再根据方法的参数来决定调用到哪个具体的实现类中,相当于一种高级别的代理。
这种模式是如何实现的呢,
查看getAdaptiveExtend的实现类发现最终调用到
createAdaptiveExtensionClassCode方法来创建该接口的具体的Class的实例,通过该Class实例来创建对应的类实例。而createAdaptiveExtensionClassCode的方法可以看到,在方法中使用java代码拼出了一个类, 在该方法主要是根据传入的接口,生成一个该接口的实现类, 而在实现类中主要根据接口中的方法中是否有Adaptive注解,如果有该注解则对生成一个该方法的代理方法,如果没有Adaptive注解,这实现的方法中抛出异常,如果有Adaptive注解则生成对应的代理后的方法,,如Protocol接口的代理类为如下
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);
}
}
如上代码中export方法是经过Adaptive标注过的方法,该方法的实现如下
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);
}
在该方法中首先获取到protocol的值, 如果没有获取到则取默认值dubbo,再调用ExtensionLoader类中的getExtensionLoader(Interface.classs).getExtension(extName),方法加载到对应的扩展类的实现,最后调用该实例的export方法。
在生成了该类的代码后, 在调用Compile的扩展类的实例的compile方法,将该类代码编译为对应的Class对象。
2. public T getExtension(String name) 方法
该方法返回对应的扩展类的实例,方法根据传入的name值来获取到该name值对应的该类的实例,
通过该方法的实现分析可知,该方法中调用createExtension方法来生成对应的扩展类的实例,在createExtension方法中调用getExtensionClasses方法获取该接口的所有的扩展类的实例, 在根据传入的name值获取到该值对应的扩展类的Class实例, 而getExtensionClasses方法的实现可以看到该方法调用loadFile方法依次从”META-INF/dubbo/internal/, META-INF/dubbo/, META-INF/services/”目录下加载以该类的全额限定名的文件,再读取该文件中的所有的key-value的值
loadFile方法的实现感觉比较复杂, 主要是函数的深度太深了。。嵌套了很多层的逻辑, 一般我司要求函数的深度都不能超过5层,但是此函数深度将近20,,看的十分费劲,不知道阿里是如何保证如此复杂的逻辑的代码可测试性的。。
该方法主要完成以下几个功能
2.1 加载对应的文件中定义的所有的类,且要求该类的所有实现类中只能有一个Adaptive类型的类,
2.2 如果该实现类包含有该接口类型的构造函数,则将该类加入到包装器类的set中
2.3 如果该类的实现类中没有该接口类型的构造函数, 则检查该类是否又Activate注解,如果有该注解则将该类加入到注解的缓存中, 同时将该类的扩展的名字和Class对象加入到缓存中。
在根据name值加载完成对应扩展类的Class对象后,使用反射实例化出来一个该类的实例, 然后将该实例加入到缓存的Map中, 在生成该类的实例后,还要执行一步注入的流程,也即是injectExtension的步骤, 是通过调用injectExtension方法来实现的, 该方法通过获取该类中的所有set方法,通过调用该方法进行属性的注入,该方法的实现如下
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
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) {
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;
}
在该段代码中最主要的一段是
Object object = objectFactory.getExtension(pt, property);
通过该方法的调用,可以获取到对应的set方法的要注入的属性的值,从而获取到该值,将该值注入到生成的对象中。
如在RegistryProtocol类中由如下的代码片段
private Cluster cluster;
public void setCluster(Cluster cluster) {
this.cluster = cluster;
}
private Protocol protocol;
public void setProtocol(Protocol protocol) {
this.protocol = protocol;
}
则在生成RegistryProtocol的实例的时候,会注入cluster的实例和protocol的实例,那么该到那儿去找这些对象的实例呢,,,是通过objectFactory中加载, 加载的类型为参数的类型,如Protocol,加载的参数为set后的方法名字的内容,如setProtocol方法加载的值为protocol。。
在inject实例完成后,还要再进行最后一步,在getFile方法的分析中我们可知,会生成每个对象的包装器类, 也即使2.2中定义的对象, 则此时需要判断包装器类是否为空,如果不为空,则需要生成对应的包装器类型,而不是原类型,同时也要对生成的包装器类型进行再一次的setMethod的注入。。
至此类的实例化完成。
我们就可以在业务代码中调用ExtensionLoader的getExtension方法获取到该接口的基于参数名的对应的实现类,通过调用getAdaptiveExtension方法获取到使用Compiler编译后的代理类的对象。。这样就可以调用对应的对象的方法来完成对应的业务逻辑
该方法的调用逻辑为
getExtension->createExtension-> getExtensionClasses-> loadFile
-> injectExtension
下一篇将分析dubbo支持的几种基本的协议