文档章节

Dubbo源码分析-微内核插件式开发(ExtensionLoader)

徐安是个好人
 徐安是个好人
发布于 2017/05/05 21:12
字数 1664
阅读 68
收藏 2

1、实现原理说明

上一个章节说道Dubbo的微内核插件式开发其实就是在SPI设计思路上做了优化升级。我们都知道SPI设计思路是当我们要获取某个接口的实现时,使用ServiceLoader去扫描第三方依赖JAR包的META-INF/services/目录,如果找到名字为这个接口全路径的文件,那么就会读取这个文件中的内容,然后SPI约定这个内容就是实现类的全路径名称,所以ServiceLoader就可以根据这个实现类路径来实例化提供服务。而Dubbo是自己实现了加载器,名字叫ExtensionLoader。Dubbo约定只有标记了@SPI注解的接口,才能使用ExtensionLoader去加载实现类。ExtensionLoader会依次扫描META-INF/dubbo/internal/目录、META-INF/dubbo/目录、META-INF/services/目录,扫描出名字为接口的全路径名的文件,然后文件内容约定key=value的形式出现,key就是这个实现类的别名,value才是实现类的全路径。当然当一个项目里面,对同一个接口可能会很多种实现,那到底使用哪种实现,所以这里Dubbo做了特别的设计,每个支持SPI接口会对应需要一个适配实现(可以自己实现,也可以系统帮你生成),  然后用户可以根据这个设配实现来决定最后到底需要选择哪个具体实现。ExtensionLoader在扫描到实现类时,并发现这个实现类上标记了@Adaptive注解时,就会把这个实现类作为适配实现看待,如果发现扫描出来的实现类里面一个都没有标记这个注解,那么ExtensionLoader会自动生成一个实现类作为该接口的设配实现,所以所有的@SPI接口在使用时都会存在一个适配实现。

2、@SPI注解

上面说到了,只有注解了SPI注解的接口,ExtensionLoader才支持加载扩展实现,SPI有个value参数,这个value指定的是扩展实现的的别名,指定后默认使用的就是这个别名对应的扩展实现。SPI源码如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
	String value() default "";
}

Dubbo内部的很多实现都基于这种方式来扩展实现的。我们都知道Dubbo支持很多序列化协议,我就拿这个例子来说明,看Protocol协议接口,他标记了SPI注解,并指定了默认实现是别名叫dubbo的协议实现。所以系统里面通过ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();获取协议实现时,如果没有特别指定用哪种实现,默认就会使用别名叫dubbo的协议。

@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();
}

3、@Adaptive注解

作用一:@Adaptive注解主要用来标记适配类实现,上面说了,ExtensionLoader在各个依赖JAR寻找实现类时,会检查实现类有没有打上@Adaptive标记,如果打上了,说明该实现类就是适配实现。就会缓存到ExtensionLoader的cachedAdaptiveClass变量里面。例如我想要自己实现一个Protocol接口的适配实现,可以这样写:

@Adaptive
public class AdapterProtocol implements Protocol {
    @Override
    int getDefaultPort(){//省略...}
    
    @Override
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException{//省略...}

    @Override
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException{//省略...}
    
    @Override
    void destroy(){//省略...}
}

然后在META-INF/services/目录下放置文件:com.alibaba.dubbo.rpc.Protocol。里面的内容设置成adapterProtocol=mypackage.AdapterProtocol。这样我们就是自己实现了一个Protocol的设配实现,当系统使用ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();去加载实现时,拿到的就是我们实现的适配类了。

作用二:@Adaptive还可以标记在接口的方法上。这种情况是,当ExtensionLoader扫描了所有实现类之后,发现没有一个是标记了@Adaptive的实现类。于是ExtensionLoader会使用javasist帮我们自动生成一个设配类。在自动实现各个接口的方式时,会根据该方法上标记的@Adaptive注解的value[]数组值来进行生成。如果没有标记@Adaptive的就抛出异常。例如上面的Protocol自动实现的类反编译后长这样:

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
    //没有打上@Adaptive的方法如果被调到抛异常
    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!")
    }
    
    //接口中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");
        //参数类中要有URL属性
        if(arg0.getUrl() == null) 
            throw newIllegalArgumentException( "com.alibaba.dubbo.rpc.Invoker argument 
            getUrl() == null");
            
        //从入参获取统一数据模型URL
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
            
        //从统一数据模型URL获取协议,协议名就是SPI扩展点实现类的key
        if(extName == null) 
            throw new IllegalStateException( "Fail to 
            getextension(com.alibaba.dubbo.rpc.Protocol) name from url("  + url.toString() + 
            ") usekeys([protocol])");
          
        //利用dubbo服务查找机制根据名称找到具体的扩展点实现
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.
        getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
    }
    //其他方法省略...
}

自动实现的设配代码大致意思是,如果方法上没有标记@Adaptive注解的,就抛出一个异常,如果有标记@Adaptive注解的,就判断接口方法有没有URL参数,如果没有就找依附在参数中的URL,然后根据@Adaptive指定的value[]数组,合成代码,代码意思大致就是在使用这个适配类时,会依次使用value[]指定的实现,如果都找不到就最后使用@SPI指定的默认实现。

4、@Activate

关于这个注解,我简单说一下。当我们实现扩展时,如果加上这个注解。然后通过这个注解可以配置一些限制条件。当系统查找扩展实现会把标记了这个注解的实现缓存起来。然后可以在适当时候,过滤出要激活的实现。@Activate注解源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    //group过滤条件,没有group就不过滤
    String[] group() default {};
    //key过滤条件,如没有设置,则不过滤
    String[] value() default {};
    //排序信息,可以不提供
    String[] before() default {};
    //排序信息,可以不提供
    String[] after() default {};
    //排序信息,可以不提供
    int order() default 0;
}

例如,源码中有个CacheFilter的,就使用了这个注解。

@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)
public class CacheFilter implements Filter {
    //省略...
}

当使用系统要使用时,这样调用就可以过滤出要被激活的实现

ExtensionLoader.getExtensionLoader(Filter.class)
                .getActivateExtension(url, new String[]{}, "value");

5、ExtensionLoader扫描缓存扩展实现流程说明

© 著作权归作者所有

徐安是个好人
粉丝 9
博文 10
码字总数 7781
作品 4
杭州
私信 提问
Dubbo源码分析(7):SPI扩展机制剖析

我们都是知道一个合格的开源框架对于扩展的支持都要是相当弹性的,Dubbo 也不例外。Dubbo采用微内核+插件体系,使得设计优雅,扩展性强。Dubbo的扩展机制是基于SPI思想来实现的,但是并没有采...

郑加威
2018/09/28
73
0
Dubbo源码分析-SPI的应用

SPI简介 SPI是Service Provider Interface的缩写,即服务提供接口(翻译出来好绕口,还是不翻译的好),实质上是接口,作用是对外提供服务。 SPI是Java的一种插件机制,可以不用修改源代码实现新...

农码人生
2018/07/24
0
0
从ExtensionLoader看Dubbo插件化

欢迎加入DUBBO交流群:259566260 之前很多人问我Dubbo插件化是怎么实现的,我都是简单回答SPI。了解SPI的人知道,它只是提供一种协议,并没有提供相关插件化实施的接口,不像OSGI那样有一成套...

Bieber
2015/05/23
6.9K
14
dubbo源码分析系列(1)扩展机制的实现

1 系列目录 - dubbo源码分析系列(1)扩展机制的实现- dubbo源码分析系列(2)服务的发布- dubbo源码分析系列(3)服务的引用- dubbo源码分析系列(4)dubbo通信设计 2 SPI扩展机制 站在一个...

乒乓狂魔
2015/09/21
13.4K
5
阿里巴巴Dubbo实现的源码分析[转]

Dubbo概述 Dubbo是阿里巴巴开源出来的一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及作为SOA服务治理的方案。它的核心功能包括: #remoting:远程通讯基础,提供对...

如何让他和
2016/08/09
105
0

没有更多内容

加载失败,请刷新页面

加载更多

《Designing.Data-Intensive.Applications》笔记 四

第九章 一致性与共识 分布式系统最重要的的抽象之一是共识(consensus):让所有的节点对某件事达成一致。 最终一致性(eventual consistency)只提供较弱的保证,需要探索更高的一致性保证(stro...

丰田破产标志
今天
6
0
docker 使用mysql

1, 进入容器 比如 myslq1 里面进行操作 docker exec -it mysql1 /bin/bash 2. 退出 容器 交互: exit 3. mysql 启动在容器里面,并且 可以本地连接mysql docker run --name mysql1 --env MY...

之渊
今天
7
0
python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
今天
5
0
OSChina 周日乱弹 —— 我,小小编辑,食人族酋长

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @宇辰OSC :分享娃娃的单曲《飘洋过海来看你》: #今日歌曲推荐# 《飘洋过海来看你》- 娃娃 手机党少年们想听歌,请使劲儿戳(这里) @宇辰OSC...

小小编辑
今天
1K
11
MongoDB系列-- SpringBoot 中对 MongoDB 的 基本操作

SpringBoot 中对 MongoDB 的 基本操作 Database 库的创建 首先 在MongoDB 操作客户端 Robo 3T 中 创建数据库: 增加用户User: 创建 Collections 集合(类似mysql 中的 表): 后面我们大部分都...

TcWong
今天
40
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部