文档章节

Dubbo解析(二)-内核实现之SPI机制(下)

青离
 青离
发布于 06/14 07:13
字数 2276
阅读 593
收藏 10
点赞 1
评论 0

上一章我们介绍了JDK的SPI机制,它旨在建立一种服务发现的规范。而Dubbo基于此根据框架的整体设计做了一些改进:

  1. JDK的SPI机制会一次性实例化所有服务提供者实现,如果有提供者的初始化很耗时,但并不会使用会很耗费资源。Dubbo则只存储了所有提供者的Class对象,实际使用时才构造对象
  2. JDK的SPI机制只在配置文件中记录了实现类的全限定名,并没有定义一个配置名。而Dubbo的服务接口往往提供多个实现方式,需要在每个服务接口中定义一个匹配方法去选择要使用哪种实现方式。这种方式不利于框架的扩展,因而规定在提供者实现类的配置文件中对每个实现类定义一个配置名,用"="隔开,形成key-value的方式。
  3. 增加了对服务接口的IOC和AOP的支持,服务接口内的其他服务接口成员直接通过SPI的方式加载注入。

Dubbo作为一个微内核+插件的框架设计,其内核就是基于SPI机制动态地组装插件。插件都是以接口的方式定义在框架中,每个接口提供的服务也称为扩展点,表示每个服务接口都可以根据不同的条件进行动态扩展,Dubbo的扩展点接口以@SPI注解标识。Dubbo自身的功能也是通过扩展点实现的,也就是Dubbo的所有功能点都可以被用户自定义扩展所代替。那么Dubbo的SPI机制究竟是怎么工作的呢?

1.SPI约定

在classpath下放置META-INF/dubbo/以接口全限定名定义的文件,内容为配置名=扩展实现类全限定名,多个实现类用换行符分隔。

META-INF/dubbo目录是针对二次开发者的,dubbo自身加载扩展点配置的目录有三个,依次是:

  1. META-INF/dubbo/internal/
  2. META-INF/dubbo/
  3. META-INF/services/

以Dubbo的协议接口Protocol为例,在dubbo-rpc-default模块中定义了DubboProtocol实现,目录为

META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol

文件中内容为

dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol

对应于dubbo的xml配置,即

<dubbo:protocol name="dubbo" />

2.扩展点适配器

JDK自带的SPI机制使用ServiceLoader加载和获取,Dubbo对于扩展点的的加载和获取则是使用ExtensionLoader。大部分的扩展点都是通过ExtensionLoader预加载一个适配类,以Protocol为例,调用方式如下:

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

getAdaptiveExtension方法创建扩展点的适配类,创建的方式分为两种

  1. 如果有且仅有一个实现类上有@Adaptive注解,则直接由其Class对象的newInstance方法创建适配类对象
  2. 如果不存在类上有@Adaptive注解的实现类,则使用字符串拼接类名为接口名$Adaptive的Class代码,默认使用javassist编译生成Class对象,然后使用newInstance方法创建适配类对象。

对于1的方式很好理解,比如Compiler接口,它的实现类AdaptiveCompiler类上就有@Adaptive注解

@Adaptive
public class AdaptiveCompiler implements Compiler {

    // 实现代码省略

}

2的方式就较为复杂,还是以Protocol举例,先看下Protocol接口的源码:

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

}

Protocol接口定义了四个方法,其中export和refer方法都有@Adaptive注解,不同于1中类上的注解,这里的注解是在方法上,这个地方要着重强调一下@Adaptive注解作用的位置。对于实现类上没有此注解,而方法上有此注解的,适用于2方式。Protocol基于2方式生成的代码如下:

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;


public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    // 没有@Adaptive注解的直接抛出UnsupportedOperationException
    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!");
    }

    // 有@Adaptive的方法,从参数里获取URL对象,
    // 获取扩展点配置名,ExtensionLoader.getExtension(extName)匹配创建扩展点实现类对象
    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0,
        com.alibaba.dubbo.common.URL arg1)
        throws com.alibaba.dubbo.rpc.RpcException {
        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.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);
    }
}

生成的适配类对象,以扩展点接口+$Adaptive命名(如Protocol$Adaptive),实现了扩展点接口。没有@Adaptive的方法直接抛出UnsupportedOperationException,对于有@Adaptive的方法,从参数中获取URL对象,然后根据URL获取扩展点配置名,再使用ExtensionLoader.getExtension(extName)匹配创建扩展点实现类对象。

这里的URL是Dubbo自定义的类,它是Dubbo的统一数据模型,穿插于系统的整个执行过程。URL的数据格式如下

dubbo://192.168.2.100:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-consumer&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&owner=william&pid=7356&side=consumer&timestamp=1416971340626

a. URL获取扩展点配置名,通过key从URL的parameter中获取

String extName = url.getParameter(key, defalutValue);

defalutValue取值于扩展点接口的@SPI的值,比如Protocol接口的defaultValue=dubbo

@SPI("dubbo")
public interface Protocol {}

key的值来自于@Adaptive的value值,如果@Adaptive没有设置value,默认为扩展点接口的类的simpleName。如果key的值为protocol时特殊处理:

String extName = url.getProtocol()

比如Transporter扩展点的bind方法

@SPI("netty")
public interface Transporter {

    @Adaptive({"server", "transporter"})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;
}

defaultValue=netty,key=server,transporter,获取配置名的方法:

String extName = url.getParameter("server",url.getParameter("transporter", "netty"));

b. 扩展点创建完$Adaptive对象,具体调用时,根据URL参数获取的配置名extName,查询对应的扩展点实现,还是以Protocol扩展点举例

ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);

getExtension(extName)方法匹配扩展点的实现类Class的配置名,找到对应的Class对象,执行newInstance方法创建实际的操作对象。extName=dubbo的Protocol扩展点实现类为DubboProtocol。创建完实现类对象,会对此对象执行injectExtension方法,即对对象内的以set开头,并且只有一个参数的public方法,执行IOC注入。

IOC注入也是以ExtensionFactory扩展点的方式实现,默认的方式是先以SPI机制获取方法参数类型的实现,如果此方法参数类型非接口或没有@SPI注解,则从Spring上下文中获取。

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

接下来判断扩展点的实现类中是否存在包装类(Wrapper),Wrapper类指存在参数为扩展点接口的构造方法的扩展点实现类。Wrapper类不是扩展点的真正实现,主要用于对真正的扩展点实现进行包装。比如Protocol的Filter包装类实现ProtocolFilterWrapper。

如果扩展点存在包装类的Class,反射调用其构造方法,将真正的扩展点实现传入,并再此执行injectExtension方法进行IOC注入。

Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
    for (Class<?> wrapperClass : wrapperClasses) {
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    }
}

至此,扩展点的调用来到了真正对应的扩展点实现类对象中。

3.扩展点自动激活

如果需要同时加载多个扩展点实现,可以使用自动激活。当扩展点的实现类上有@Activate注解,标识它是一个激活类。@Activate可以配置group和value值,ExtensionLoader的getActivateExtension方法匹配并获取符合条件的所有激活类。Dubbo内部使用激活类的主要在Protocol的Filter和Listener,后面再详细介绍。

4.配置文件的加载

以上讨论的扩展点的适配类,包装类,激活类的实现,都要基于对SPI机制的配置文件的加载。Dubbo在获取扩展点的任何扩展时,都会先判断是否加载了配置文件,如果没有,即执行loadExtensionClasses方法加载,loadExtensionClasses方法中对Dubbo的三个配置目录分别加载,调用loadFile方法。

loadFile方法查找目录下扩展点接口的全限定名的文件,过滤掉"#"的注释内容,然后每行以"="分隔,获取配置名和扩展点实现类的全限定名。

依次判断是否为Adaptive适配类,是否为Wrapper包装类,是否为Activate激活类,并存储到对应的变量或集合中去。详细操作可见ExtensionLoader.loadFile方法。

最后贴上getExtension(name)方法的整体活动图,来自大神的博客https://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235

image

© 著作权归作者所有

共有 人打赏支持
青离
粉丝 263
博文 47
码字总数 104472
作品 0
海淀
后端工程师
动手学dubbo之Container与SPI

在动手学dubbo之初体验一文中我们了解了dubbo的架构,接下来的几篇文章我会根据阅读Quick Start里面的demo源码来深入学习dubbo的实现。这一篇主要学习Container的原理、实现和作用。 一、从启...

ginobefun ⋅ 2017/07/13 ⋅ 0

dubbo 视频教程

深度解剖dubbo源码 为什么要学习dubbo 源码? 1.如果你想深入学习SOA的微服务架构设计,那通过读dubbo源码是一条非常不错的通往SOA架构设计之路,毕竟SOA的服务治理就是dubbo首先提出来的,比...

qq594295b3c16b8 ⋅ 2017/06/16 ⋅ 0

dubboSPI机制浅谈

本文重点讲述SPI机制,从jdk和dubbo 1、jdk spi机制 2、dubbo spi实现 首先spi是什么? SPI是为某个接口寻找服务实现的机制。为了实现在模块装配的时候能不在程序里动态指明,这就需要...

hyssop ⋅ 2015/11/16 ⋅ 0

聊聊Dubbo-Dubbo可扩展机制实战

1. Dubbo的扩展机制 在Dubbo的官网上,Dubbo描述自己是一个高性能的RPC框架。今天我想聊聊Dubbo的另一个很棒的特性, 就是它的可扩展性。 如同罗马不是一天建成的,任何系统都一定是从小系统不...

中间件小哥 ⋅ 05/29 ⋅ 0

聊聊Dubbo - Dubbo可扩展机制实战

摘要: 在Dubbo的官网上,Dubbo描述自己是一个高性能的RPC框架。今天我想聊聊Dubbo的另一个很棒的特性, 就是它的可扩展性。 1. Dubbo的扩展机制 在Dubbo的官网上,Dubbo描述自己是一个高性能...

阿里云云栖社区 ⋅ 06/04 ⋅ 0

dubbo源码分析系列(1)扩展机制的实现

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

乒乓狂魔 ⋅ 2015/09/21 ⋅ 5

Dubbo 实现原理与源码解析系列 —— 精品合集

摘要: 原创出处 http://www.iocoder.cn/Dubbo/good-collection/ 「芋道源码」欢迎转载,保留摘要,谢谢! 1.【芋艿】精尽 Dubbo 原理与源码专栏 2.【老徐】RPC 专栏 3.【肥朝】Dubbo 源码解析...

芋道源码掘金Java群217878901 ⋅ 前天 ⋅ 0

dubbo源码分析系列——dubbo的SPI机制源码分析

dubbo的SPI机制 关于dubbo的SPI机制请参阅dubbo开发者文档 -> http://dubbo.io/Developer+Guide-zh.htm#DeveloperGuide-zh-%E6%89%A9%E5%B1%95%E7%82%B9%E5%8A%A0%E8%BD%BD 我只把我自己理解......

杨武兵 ⋅ 2016/06/05 ⋅ 4

dubbo源码解析-spi(二)

前言 上一篇简单的介绍了的基本一些概念,在末尾也提到了,对jdk的spi进行了一些改进,具体改进了什么,来看看文档的描述 JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很...

肥朝 ⋅ 01/06 ⋅ 0

dubbo源码解析-spi(一)

前言 虽然标题是dubbo源码解析,但是本篇并不会出现dubbo的源码,本篇和之前的dubbo源码解析-简单原理、与spring融合一样,为dubbo源码解析专题的知识预热篇. 插播面试题 你是否了解,讲一讲什么...

肥朝 ⋅ 2017/12/31 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Linux中的端口大全

1 被LANA定义的端口 端口 名称 描述 1 tcpmux TCP 端口服务多路复用 5 rje 远程作业入口 7 echo Echo 服务 9 discard 用于连接测试的空服务 11 systat 用于列举连接了的端口的系统状态 13 d...

寰宇01 ⋅ 16分钟前 ⋅ 0

Confluence 6 如何备份存储文件和页面信息

备份的 ZIP 文件包含有 entities.xml,这个 XML 文件包含有 Confluence 的所有页面内容和存储附件的目录。 备份 Zip 文件结构 页面的附件是存储在附件存储目录中的,通过页面和附件 ID 进行识...

honeymose ⋅ 18分钟前 ⋅ 0

【每天一个JQuery特效】根据状态确定是否滑入或滑出被选元素

主要效果: 本文主要采用slideToggle()方法实现以一行代码同时实现以展开或收缩的方式显示或隐藏被选元素。 主要代码如下: <!DOCTYPE html><html><head><meta charset="UTF-8">...

Rhymo-Wu ⋅ 22分钟前 ⋅ 0

度量.net framework 迁移到.net core的工作量

把现有的.net framework程序迁移到.net core上,是一个非常复杂的工作,特别是一些API在两个平台上还不能同时支持。两个类库的差异性,通过人工很难识别全。好在微软的工程师们考虑到了我们顾...

李朝强 ⋅ 27分钟前 ⋅ 0

请不要在“微服务”的狂热中迷失自我!

微服务在过去几年一直是一个非常热门的话题(附录1)。何为“微服务的疯狂”,举个例子: 众所周知,Netflix在DevOps上的表现非常棒。Netfix可以做微服务。因此:如果我做微服务,我也将非常...

harries ⋅ 29分钟前 ⋅ 0

oAuth2 升级Spring Cloud Finchley.RELEASE踩坑分享

背景 6.19号,spring团队发布了期待已久的 Spring Cloud Finchley.RELEASE 版本。 重要变化: 基于Spring Boot 2.0.X 不兼容 Spring Boot 1.5.X 期间踩过几个坑,分享出来给大伙,主要是关于...

冷冷gg ⋅ 59分钟前 ⋅ 0

OSChina 周一乱弹 —— 理发师小姐姐的魔法

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @冰冰棒- :分享田馥甄的单曲《My Love》 《My Love》- 田馥甄 手机党少年们想听歌,请使劲儿戳(这里) @Li-Wang :哎,头发又长了。。。又要...

小小编辑 ⋅ 今天 ⋅ 8

Kafka1.0.X_消费者API详解2

偏移量由消费者管理 kafka Consumer Api还提供了自己存储offset的功能,将offset和data做到原子性,可以让消费具有Exactly Once 的语义,比kafka默认的At-least Once更强大 消费者从指定分区...

特拉仔 ⋅ 今天 ⋅ 0

NEO智能合约之发布和升级(二)

接NEO智能合约之发布和升级(一),我们接下来说说智能合约的升级功能。 一 准备工作 合约的升级需要在合约内预先设置好升级接口,以方便在升级时调用。接下来我们对NEO智能合约之发布和升级...

红烧飞鱼 ⋅ 今天 ⋅ 0

个人博客的运营模式能否学习TMALL天猫质量为上?

心情随笔|个人博客的运营模式能否学习TMALL天猫质量为上? 中国的互联网已经发展了很多年了,记得在十年前,个人博客十分流行,大量的人都在写博客,而且质量还不错,很多高质量的文章都是在...

原创小博客 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部