Dubbo源码分析(三)---SPI机制的分析

279 阅读4分钟

本次分析的Dubbo源码版本 2.7.3-release
Dubbo的架构思想是微内核加插件化的思想,我们可以动态去替换某一个组件, 例如:Dubbo提供redis.zookeeper、consul、etcd等注册中心,我们可以根据我们需要去修改配置使用哪个注册中心而不需要修改任何代码.就以分析服务导出为例,其中服务协议导出所有的Protoco就是通过Dubbo自己实现的SPI机制加载的,它是借鉴JDK的SPI机制的增强,这里可以看到ExtensionLoader是SPI的核心类,传入扩展接口类(注意:这里这里SPI类必须是接口),getAdaptiveExtension方法就是获取具体的实现类;

ExtentionLoader的getExtensionLoader方法就是传入Protocol参数, EXTENSION_LOADERS是缓存type和ExtentionLoader的键值,如果不存在,就创建extentionLoader对象.
ExtensionLoader构造函数传入是Prtocal这个接口类型,所以这个ObjectFactory是ExtensionFactory的扩展类
getAdaptiveExtension就是获取适配的扩展类实例的实现,在初始化的时候cachedAdaptiveInstance是空,这里使用双重检查防止多线程的的并发导致对象重复创建,开始加载扩展类时,会执行createAdaptiveExtension创建适配的扩展类方法.
这里调用getAdaptiveExtensionClass获取适配的扩展类,然后利用反射进行创建对象, 然后调用injectExtension然后set方法,自动注入的扩展类对象.
getAdaptiveExtensionClass方法首先会调用getExtensionClasses获取所有的Protocol接口实现类的扩展类,然后判断是否存带有@Adaptive的扩展类,如果没有则执行createAdaptiveExtensionClass创建适配的可扩展类.
这里初始化加载的cachedClasses是null的,这里还是使用双重检查判断是否为空,然后执行loadExtensionClasses方法扩展的class类,然后缓存到cachedClasses.
loadExtensionClasses首先缓存默认的扩展的名称, 例如:Protocol中注解是 @SPI("dubbo"),cachedDefaultName就是缓存dubbo这个字符串(如果有多个默认名称这里只会使用第一个作为默认名称),接着扫描jar下resource目录下三个目录分别是"META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/,这个三个目录的扫描还做了com.alibaba和org.apache包的兼容

接下来就看下具体加载扩展类的过程

首先,找到类加载器ClassLoader,然后classLoader的getResources方法找所有的扩展类的URL,然后调用loadResource加载资源.

loadResource主要是对配置文件的加载和解析, 配置文件都是key=value, key是自定义 的, value就是Protocol的实现类,如果有value则掉哟胳loadClass加载并初始化实现类
loadClass判断扩展咧是否传入Protocol的子类,从这里看出配置扩展类一定是接口的实现类,然后判断配置的扩展类是否带有Adaptive,如果有则缓存到cachedAdaptiveClass变量中, 如果是Protocol的包装类(就是构造函数需要传入Protoco类型),则缓存到cacheWrapperClass,其他情况,则缓存到extensionClasses.
现在回顾之前的getAdaptiveExtensionClass方法时,执行完getExtensionClasses方法后,cachedAdaptiveClass是否为空, 可以看出如果扩展类是直接带有@Adaptive注解, 这个地方会直接返回配置的扩展类, 否则createAdaptiveExtensionClass闯进可扩展
这里会通过AdaptiveClassCodeGenerator的generate生成扩展的类,然后调用Compiler的扩展类,这里默认是使用JavassistCompiler来编译拼接的类,
下面就贴下Protocol生成的扩展类Protocol@Adaptive的源码. 可以看到接口上带有 @Adaptive注解的,都是可以通过URL里面的参数设置的不同,动态实现对不同的扩展类 调用,这也许是JDK的SPI的只能对接口的扩展最大改进,Dubbo的SPI可以实现对方法的扩展.

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public void destroy()  {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort()  {
throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}

总结
今天对于Dubbo的SPI机制做了一个全过程分析,Dubbo的SPI的既支持类的扩展又支持方法的扩展的,粒度比JDK的SPI更加细.