您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

Dubbo自适应扩展机制

时间:2020-09-07 11:17:24  来源:  作者:

Dubbo自适应扩展机制

Dubbo里除了Service和Config层为API,其它各层均为SPI。相比于JAVA中的SPI仅仅通过接口类名获取所有实现,Dubbo的实现可以通过接口类名和key值来获取一个具体的实现。通过SPI机制,Dubbo实现了面向插件编程,只定义了模块的接口,实现由各插件来完成。

1. 使用方式

1.1 Java SPI

在扩展类的jar包内,放置扩展点配置文件META-INF/service/接口全限定名,内容为:扩展实现类全限定名,多个实现类用换行符分隔。

如下为MySQL中Driver接口的实现:

Dubbo自适应扩展机制

 

package com.mysql.jdbc;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    ... }

调用时使用ServiceLoader加载所有的实现并通过循环来找到目标实现类

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();try{
    while(driversIterator.hasNext()) {
        driversIterator.next();    }} catch(Throwable t) {
// Do nothing
}

1.2 Dubbo SPI

拿Dubbo中Protocol接口来说,Protocol的定义如下:

package org.Apache.dubbo.rpc;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI("dubbo")
public interface Protocol {    int getDefaultPort();
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;    void destroy();
}

需要指出的是Invoker继承了Node接口,而Node接口提供了getUrl方法,每个方法都能从入参获得URL对象

public interface Node {
    URL getUrl();
    boolean isAvailable();
    void destroy();
}

要求

1.接口上有org.apache.dubbo.common.extension.SPI注解,提供默认的实现

2.对于支持自适应扩展的方法要求方法入参能获得org.apache.dubbo.common.URL对象,同时方法上有org.apache.dubbo.common.extension.Adaptive注解,Adaptive注解可以提供多个key名,以便从URL中获取对应key的值,从而匹配到对应的实现(这里Protocol比较特别,没有提供key名也能根据URL来动态获取实现,后面会说明)

3.在扩展类的jar包内,放置扩展点配置文件META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。如Protocol的默认实现:

Dubbo自适应扩展机制

 

注意:META-INF/dubbo/internal为Dubbo内部实现的配置文件路径

调用时通过ExtensionLoader根据需要来选择具体的实现类,

ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);

可选方式包括

1.选择默认的实现

Protocol protocol = loader.getDefaultExtension();

2.根据指定key选择实现

Protocol protocol = loader.getExtension("dubbo");

3.根据URL参数动态获取实现

Protocol protocol = loader.getAdaptiveExtension();

2. Dubbo SPI 特性

Dubbo对Java中的标准SPI进行了扩展增强,官方文档中提到其提供了如下特性:

  • 扩展点自动包装
  • 扩展点自动装配
  • 扩展点自适应
  • 扩展点自动激活

在介绍各个特性前先介绍下大概的内部实现。从上面的使用中可以看到Dubbo对SPI扩展的主要实现在ExtensionLoader类中,关于这个类源码的讲解可以看官方文档,讲解的很详细,这边主要说下大概过程:

  1. 根据传入的类型从classpath中查找META-INF/dubbo/internal和META-INF/dubbo路径下所有对应的扩展点配置文件
  2. 读取扩展点配置文件中所有的键值对
  3. 根据键值对缓存Class对象,如果类有同类型参数的构造函数,则为包装类,会缓存在另一个容器中
  4. 实例化对象,缓存后并返回

2.1 扩展点自动包装

在返回真正实例前,会用该类型的包装类进行包装,既采用装饰器模式进行功能增强。

if (CollectionUtils.isNotEmpty(wrApperClasses)) {
    // 循环创建 Wrapper 实例
    for (Class<?> wrapperClass : wrapperClasses) {
        // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
        // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    }
}

其实就是使用静态代理来实现AOP

Dubbo自适应扩展机制

 

2.2 扩展点自动装配

返回实例前会遍历所有的setXXX方法,判断set方法参数是否存在自适应对象,如果存在则通过ExtensionLoader加载自适应对象然后进行赋值,可以通过方法上加org.apache.dubbo.common.extension.DisableInject注解来屏蔽该功能。该功能其实就是实现了IOC,具体可以看ExtensionLoader的injectExtension方法。

2.3 扩展点自适应

同上面介绍的一样,Dubbo的SPI可以根据传入的URL参数中携带的数据来动态选择具体的实现。

2.4 扩展点自动激活

上面介绍过,Adaptive注解是加在方法上的,类似的有个注解org.apache.dubbo.common.extension.Activate是加在实现类上。当加在Class上时,ExtensionLoader会将对应的key和Class信息缓存到另一个容器中,后续可以通过ExtensionLoader获取某一类的实现列表,既如下方法

public List<T> getActivateExtension(URL url, String[] values){
    ...}

3. ExtensionLoader自适应扩展机制

ExtensionLoader自适应扩展机制的大概实现逻辑是这样的:Dubbo会为拓展接口生成具有代理功能的代码,然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。最后再通过反射创建代理类,在代理类中,就可以通过URL对象的参数来确定到底调用哪个实现类。主要实现在createAdaptiveExtensionClass方法中。

private Class<?> createAdaptiveExtensionClass() {
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    ClassLoader classLoader = findClassLoader();    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

上面的Protocol接口经过处理后的内容如下:

public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    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.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 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);
    }}

前面提到过,Protocol是一个比较特殊的接口,Adaptive注解里没有指定Key,因而没法通过URL携带的参数获取具体的实现类。从上面处理过后的内容来看,可以看到,对于Protocol接口直接使用的URL的protocol属性字段的值来进行判断的。

如下例子是在Adaptive里有提供key的实现处理过后的内容:

public java.lang.String sayHello(org.apache.dubbo.common.URL arg0) {
    if (arg0 == null) {
        throw new IllegalArgumentException("url == null");
    }    org.apache.dubbo.common.URL url = arg0;    String extName = url.getParameter("keyA", "dobbo");
    if (extName == null) {
        throw new IllegalStateException("Failed to get extension (cn.demo.spi.Factory) name from url (" + url.toString() + ") use keys([keyA])");
    }    cn.demo.spi.Factory extension = (cn.demo.spi.Factory) ExtensionLoader.getExtensionLoader(cn.demo.spi.Factory.class).getExtension(extName);
    return extension.sayHello(arg0);
}

可以发现这种情况直接使用的URL的getParameter方法从携带的参数中获取对应的值。

通过查看AdaptiveClassCodeGenerator的实现可以发现该类的generateExtNameAssignment里对protocol做了特殊的判断。AdaptiveClassCodeGenerator完成了代理类内容的创建,大概过程为:根据原接口定义(type),包装出根据URL参数动态调用getExtension方法的实现类,然后将动作实际委托给了ExtensionLoader.getExtensionLoader(type).getExtension(extName);方法。其中extName为方法上的Adaptive注解指定的key的对应值,获取过程为:

  1. 如果Adaptive注解配置的key为空,则使用类名作为扩展名
  2. 如果扩展名为protocol,则从URL的protocol里获取对应的值
  3. 如果扩展名不为protocol,且方法参数里有org.apache.dubbo.rpc.Invocation,则从URL.getMethodParameter里获取
  4. 以上都没有则直接从URL.getParameter中获取。

Protocol接口由于没有在Adaptive注解里指定key,则会使用类名protocol作为默认的扩展名,从而命中第2条规则。

通过自适应扩展机制,Dubbo框架的各层的核心实现都是基于接口的,而将具体的实现下放到插件中。根据加载的插件不同,各层能够选择适合的实现而不会影响核心的逻辑流程。如Protocol接口,表示通信协议,可选的实现包括dubbo、rmi、http等。



Tags:Dubbo   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
第一部分介绍生产上出现Dubbo服务拥堵的情况,以及Dubbo官方对于单个长连接的使用建议。 第二部分介绍Dubbo在特定配置下的通信过程,辅以代码。 第三部分介绍整个调用过程中与性能相关的一些参数。 第四部分通过调整...【详细内容】
2021-06-18  Tags: Dubbo  点击:(123)  评论:(0)  加入收藏
1. Dubbo简介及线程池策略Apache Dubbo 是一款高性能、轻量级的开源 Java 服务框架。提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现...【详细内容】
2021-05-18  Tags: Dubbo  点击:(202)  评论:(0)  加入收藏
Dubbo里除了Service和Config层为API,其它各层均为SPI。相比于Java中的SPI仅仅通过接口类名获取所有实现,Dubbo的实现可以通过接口类名和key值来获取一个具体的实现。通过SPI...【详细内容】
2020-09-07  Tags: Dubbo  点击:(62)  评论:(0)  加入收藏
Dubbo(来自于阿里巴巴)Dubbo是一个分布式服务框架,致力于提供高性能和透明化的PRC远程调用服务调用方案。 Dubbo的的特点 通过spring配置的方式即可完成服务化,对于应用无入侵。...【详细内容】
2020-08-10  Tags: Dubbo  点击:(41)  评论:(0)  加入收藏
概述Dubbo支持同一服务向多个注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是也支持自定义扩...【详细内容】
2020-07-18  Tags: Dubbo  点击:(54)  评论:(0)  加入收藏
导读:在分布式系统中,远程调用是最基础也是最重要的基石。历史上,曾经先后出现过 CORBA、RMI、EJB、WebService 等技术和规范,在服务化以及微服务日趋流行的今天,更多的被广泛使...【详细内容】
2020-07-13  Tags: Dubbo  点击:(65)  评论:(0)  加入收藏
概述Dubbo的配置项非常多,但所有配置项分为三大类,服务发现、服务治理、性能调优。服务发现类的配置项,表示该配置项用于服务的注册与发现,目的是让消费方找到提供方;服务治理类...【详细内容】
2020-07-13  Tags: Dubbo  点击:(95)  评论:(0)  加入收藏
前言这周收到外部合作同事推送的一篇文章,【漏洞通告】Apache Dubbo Provider默认反序列化远程代码执行漏洞(CVE-2020-1948)通告。按照文章披露的漏洞影响范围,可以说是当前所有...【详细内容】
2020-07-09  Tags: Dubbo  点击:(118)  评论:(0)  加入收藏
0x01 漏洞背景2020年06月23日, 360CERT监测发现 Apache Dubbo 官方 发布了 Apache Dubbo 远程代码执行 的风险通告,该漏洞编号为 CVE-2020-1948,漏洞等级:高危。Apache Dubbo...【详细内容】
2020-06-24  Tags: Dubbo  点击:(47)  评论:(0)  加入收藏
不知道你是否在工作中有遇到过类似情况: dubbo接口调试复杂,需要通过telnet命令或者通过consumer调用来触发。 telnet语句参数格式复杂,每次编写都要小心谨慎,一旦出错又需重来...【详细内容】
2020-06-18  Tags: Dubbo  点击:(59)  评论:(0)  加入收藏
▌简易百科推荐
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(1)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(9)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(19)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(23)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(24)  评论:(0)  加入收藏
一个项目的大部分API,测试用例在参数和参数值等信息会有很多相似的地方。我们可以复制API,复制用例来快速生成,然后做细微调整既可以满足我们的测试需求1.复制API:在菜单发布单...【详细内容】
2021-12-14  AutoMeter    Tags:AutoMeter   点击:(20)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条