dubbo原理和机制_用最清晰明了的方式讲述Dubbo的SPI机制

懂得Dubbo的同学一听到Dubbo肯定会有两个反应:1.Dubbo是高性能RPC调用的框架;2.Dubbo的SPI机制是Dubbo实现插件式扩展的基础;

而Dubbo中有一个重要的设计理念就是微内核、富插件模式。所谓的微内核、富插件就是内核提供特定的接口,而组件则只需要满足接口的特定要求就可以灵活的接入,并且管理这些接入的插件,但是这些组件可以独立的发展、更改(不会对现有系统造成改动)。开发中常用的idea就有插件市场,还有像VSCode等,我们都可以从插件市场中选择需要的插件安装即可使用。

那对于高度扩展性的设计理念Dubbo是怎么实现的呢?官网中Dubbo的扩展点高达几十个!!!

5c60085970a4c3872730d5688e58f885.png

图1:SPI扩展实现

Java SPI使用以及不足之处

再说Dubbo SPI前,我们还是有必要先说一下Java的SPI机制(做一下预热,因为Dubbo的SPI机制功能更加强大也更加复杂);

对于Java SPI的使用是比较简单的(Java SPI实现原理以后有时间的话可以给大家再说一下);

1.首先我们肯定是要先定义扩展接口;

public interface Computer {  // 打开电脑    void start();}

2.我们再需要定义一些实现类,我这里实现两个意思一下;

public class Mac implements Computer {    @Override    public void start() {        System.out.println("你的mac电脑正在打开......");    }}public class Thinkpad implements Computer {    @Override    public void start() {        System.out.println("你的Thinkpad电脑正在打开......");    }}

3.然后再在项目reources目录下新建一个META-INF/services文件夹,然后再新建一个以Computer接口的全限定名命名的文件,文件内容如下所示;

com.demo.spi.Maccom.demo.spi.Thinkpad

4.上面的步骤就类似于烧饭时我们的原材料已经准备完毕,下面我们需要再使用锅进行炒菜;这边的一个关键就是使用ServiceLoader的load方法去创建对应的服务加载器,然后通过服务加载器去加载实例;

ServiceLoader serviceLoader = ServiceLoader.load(Computer.class);Iterator iterator = loadedParsers.iterator();while (iterator.hasNext()){  Computer computer = iterator.next();  Computer.start();}

从上面的形式可以看出来原生的Java SPI机制缺点就是会一次性加载所有的扩展实现,如果有的扩展吃实话很耗时,但也没用上很浪费资源。

Dubbo SPI实现逻辑

现在回到正题就是Dubbo是如何对Java的SPI机制进行改进的?我这里总结以下几点:

1.Java SPI 会一次性实例化扩展点所有实现,但是Dubbo可以实现按需加载实现类;

2.如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。

3.增加了对扩展点IOC和AOP的支持,一个扩展点可以直接setter 注入其它扩展点。

4.Dubbo的扩展机制能很好的支持第三方IoC容器,默认支持Spring Bean。

在以上改进项中,第一个改进项比较好理解。第二个改进项没有进行验证,就不多说了。第三、四个改进项是增加了对 IOC 和 AOP 的支持。

下面就说一下Dubbo的SPI实现原理(Dubbo SPI使用我这边就不仔细说了,之前Dubbo负载负载均衡和容错机制文章中都有提到):

1.我们首先通过ExtensionLoader(我们叫ExtensionLoader为扩展点加载器)的getExtensionLoader方法获取一个ExtensionLoader实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。

T t = ExtensionLoader.getExtensionLoader(T.class).getExtendsion();

getExtensionLoader(...)用于判断接口上是否有@SPI注解(扩展点)和类型必须是接口。

public static  ExtensionLoader getExtensionLoader(Class type) {  if (type == null) {    throw new IllegalArgumentException("Extension type == null");  } else if (!type.isInterface()) { // 是否是接口    throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");  } else if (!withExtensionAnnotation(type)) { // 是否标记有SPI注解    throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");  } else {    // 这里加了缓存机制    ExtensionLoader loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);    if (loader == null) {      EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));      loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);    }    return loader;  }}

2. 根据扩展实现类的别名name调用getExtension(String name)方法获取扩展对象(如果传入的name为"true",则获取默认的扩展实现类实例,即通过@SPI注解的value属性指定的默认扩展实现类别名,此时如果未设置value值则返回 null)。

public T getExtension(String name) {  if (StringUtils.isEmpty(name)) {    throw new IllegalArgumentException("Extension name == null");  } else if ("true".equals(name)) { // 默认扩展实现对象    return this.getDefaultExtension();  } else {    Holder holder = this.getOrCreateHolder(name);    Object instance = holder.get();    if (instance == null) {      synchronized(holder) {        instance = holder.get();        // 创建扩展实现类        if (instance == null) {          instance = this.createExtension(name);          holder.set(instance);        }      }    }    return instance;  }}

3.创建扩展实现类主要分为以下几个步骤:

3.1.加载扩展实现类;

3.2.反射创建扩展实现类的实例对象;

3.3.实例对象属性注入;

3.4.遍历包装类列表 cachedWrapperClasses,创建包装类实例,并注入依赖。

private T createExtension(String name) {  // 加载扩展实现类clazz(这里也是有Map缓存的)  Class> clazz = (Class)this.getExtensionClasses().get(name);  if (clazz == null) {    throw this.findException(name);  } else {    try {      // 反射创建扩展实现类的实例对象(这里也是有Map缓存)      T instance = EXTENSION_INSTANCES.get(clazz);      if (instance == null) {        EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());        instance = EXTENSION_INSTANCES.get(clazz);      }      // 注入实例对象属性值      this.injectExtension(instance);      // 遍历包装类列表 cachedWrapperClasses,创建包装类实例,并注入依赖      Set> wrapperClasses = this.cachedWrapperClasses;      Class wrapperClass;      if (CollectionUtils.isNotEmpty(wrapperClasses)) {        for(Iterator var5 = wrapperClasses.iterator(); var5.hasNext(); instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance))) {          wrapperClass = (Class)var5.next();        }      }      this.initExtension(instance);      return instance;    } catch (Throwable var7) {      throw new IllegalStateException("Extension instance (name: " + name + ", class: " + this.type + ") couldn't be instantiated: " + var7.getMessage(), var7);    }  }}

Dubbo中SPI加载路径(ExtensionLoader中直接可以看到):

META-INF/dubbo/internal/META-INF/dubbo/META-INF/services/

其实到这里Dubbo的SPI实现逻辑基本就已经结束了,但是Dubbo为了动态化、可以实时选择不同的扩展类又引入了一套自适应扩展机制。

Dubbo自适应扩展机制

Dubbo的自适应扩展机制从本质上的实现原理还是代理模式。

首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。最后再通过反射创建代理类,在代理类中,就可以通过URL对象的参数来确定到底调用哪个实现类。

自适应扩展使用(以负载均衡为例):

// 先在SPI基础上,在需要的方法或者类加上@Adaptive注解@SPI("dubbo")public interface Protocol {    int getDefaultPort();    @Adaptive     Exporter export(Invoker invoker) throws RpcException;    @Adaptive     Invoker refer(Class type, URL url) throws RpcException;// 获取动态拓展对象ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

在getAdaptiveExtension中会进行javassist动态编译,编译完之后就可以根据入参的URL进行判断选择哪个Name,再进行ExtensionLoader.getExtensionLoader(Class).getExtension(name),正常调用;

// 进行动态编译private Class> createAdaptiveExtensionClass() {  String code = (new AdaptiveClassCodeGenerator(this.type, this.cachedDefaultName)).generate();  ClassLoader classLoader = findClassLoader();  Compiler compiler = (Compiler)getExtensionLoader(Compiler.class).getAdaptiveExtension();  return compiler.compile(code, classLoader);}

我们直接看一下编译后的代码大致的样子:

public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {      // ......      // 典型的代理模式    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {        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的整套SPI机制已经将完,大家可以试着自己基于Spring做一套Spring SPI机制,友情提示一下可以基于FactoryBean、代理模式和Map做缓存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值