【Dubbo 源码解析】02_Dubbo SPI

Dubbo SPI:(version:2.6.*)

Dubbo 微内核 + 插件 模式,得益于 Dubbo SPI 。其中 ExtentionLoader是 Dubbo SPI 最核心的类,它负责扩展点的加载和生命周期管理。

ExtensionLoader

ExtensionLoader 类似于 Java SPI 的 ServiceLoader,负责扩展的加载和生命周期维护,它是 Dubbo SPI 最核心的类。

使用最频繁的 API 有如下几个:

  • public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type)

    -- 获取 type 类对应的 ExtensionLoader

  • public T getAdaptiveExtension()

    -- 获取扩展自适应实例(扩展适配器)。扩展点适配器的实现中,一般会调用下面的 API 来获取指定的扩展点实例

    warn: dubbo 中大多数的扩展实例,都是通过扩展点匹配器 AdaptiveExtension 来获取的

  • public T getExtension(String name)

    -- 根据 name 获取指定的扩展实例

  • public T getDefaultExtension()

    -- 获取 @SPI value 中指定的默认扩展点

  • public List<T> getActivateExtension(URL url, String key)

    -- 获取被激活的扩展点集合

举例:

  • Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); -- 获取扩展点适配器。扩展点适配器实现类中会调用下面的代码来获取指定的扩展点实例
  • Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName); -- 获取指定的扩展点
  • Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getDefaultExtension(); -- 获取 @SPI value 中指定的默认扩展点
  • ExtensionLoader.getExtensionLoader(ExporterListener.class).getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)) -- 获取被激活的扩展点集合

Dubbo 扩展点机制基本概念

  • 扩展点(Extension Point)

    是一个Java的接口。

  • 扩展(Extension)

    扩展点的实现类。

  • 扩展实例(Extension Instance)

    扩展点实现类的实例。

  • 扩展自适应实例(Extension Adaptive Instance)

    扩展适配器。扩展适配器实例就是一个 Extension 的代理,它实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪个扩展。

  • @SPI

    @SPI 注解作用于扩展点的接口上,表明该接口是一个扩展点,可以被 Dubbo 的ExtentionLoader 加载。如果没有此注解的话, ExtensionLoader.getExtensionLoader(type) 调用会异常。

  • @Adaptive
    1. @Adaptive 注解使用在类上,表示这个类是一个扩展适配器。当调用 ExtensionLoader.getExtensionLoader(type).getAdaptiveExtension() 会获取到该扩展适配器的实例。
    2. @Adaptive 注解使用在方法上,表示该方法是一个适配(自适应)方法。Dubbo 在为扩展点生成扩展适配器时,如果方法上有 @Adaptive 注解,会为该方法生成对应的实现。实现方法内部会根据方法的参数,来决定使用哪个扩展。

举例: 以 com.alibaba.dubbo.rpc.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 类上有 @SPI("dubbo"), 含有 4 个方法,其中有 2 个有 @Adaptive 注解。

  1. @SPI("dubbo")

    @SPI 表示 Protocol 是一个扩展点。如果 Protocol 类上没有 @SPI 注解的话,试图去加载扩展时,会抛出异常。

    "dubbo" 表示 Protocol 的默认扩展点的名称是 "dubbo"

  2. @Adaptive

    这里 @Adaptive 用在方法上,表示 export() 与 refer() 方法都是适配方法,Dubbo 会自动为该方法生成适配实现。

    而 getDefaultPort() 和 destroy() 方法上没有 @Adaptive 注解,则 Dubbo 在自动生成扩展适配器时,会让这类方法抛出异常。

说明:

  • Dubbo 自动生成扩展实现的代码参考 ExtensionLoader#createAdaptiveExtensionClassCode() 方法

    通过阅读源码,我们可以发现,Dubbo 是通过扩展点适配器在运行时动态选择扩展点实现的,而动态选择的策略是通过 URL 中的参数来决定的。

  • Duubo 为 Protocol 生成的扩展点适配器(Protocol$Adaptive.class)的代码如下:
package com.alibaba.dubbo.rpc;
?
import com.alibaba.dubbo.common.extension.ExtensionLoader;
?
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    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!");
    }
?
    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);
    }
?
    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);
    }
}

源码分析

根据代码 ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension() 来分析 SPI 的加载过程:

#getExtensionLoader(Protocol.class)

  1. new 出一个 ExtensionLoader,并将 Protocol.class 赋值给成员变量 type

#getAdaptiveExtension() 获取 SPI 扩展点适配器

#getExtensionClasses() 首先加载 type 类相关的所有扩展点实例

  1. 获取 Protocol.class 上的 @SPI 注解,如果 value() 有且只有一个值,就将值存放到 cachedDefaultName 中;如果没有值,就跳过。 cachedDefaultName 就是 @SPI 默认实现的扩展点 name
  2. 按目录顺序加载 SPI 扩展文件,扩展文件名为:com.alibaba.dubbo.rpc.Protocol 目录顺序为: META-INF/dubbo/internal/ META-INF/dubbo/ META-INF/services/
  3. 依次读取文件中的内容,内容格式有两种:kv格式 和 v 格式 dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol com.alibaba.dubbo.rpc.protocol.http.HttpProtocol k: 扩展点的 name (name 有多个值的话,用逗号","隔开) v: 扩展点对应的 class

a. 如果 v 类上有 @Adaptive 注解,就将 v 赋值给 cachedAdaptiveClass ? b. 如果 v 类是装饰类,那么就将 v 添加到 cachedWrapperClasses 中 ? 装饰类:v 类含有一个参数的构造函数,且这个参数为 SPI 接口 type,这里为 Protocol.class ? clazz.getConstructor(type); ? c. 如果前端的情况都不满足,且 v 有默认的构造函数 ? 则检查扩展点的 name 是否有值,没有值的话就,取 v 上的 @Extension 注解的 value();没有 @Extension 注解,就取 v 类的简称的小写(simpleName - type.simpleName) ? 获取 v 类上的 @Activate 注解,有值的话就存放到 cachedActivates 里面(cachedActivates.put(names[0], activate),@Activate 是用来激活指定的扩展点的) ? 将 v 缓存到 cachedNames 中 (cachedNames.put(clazz, name)) ? 将 v 缓存到 cachedClasses 中 (extensionClasses.put(name, clazz))

#createAdaptiveExtensionClass()

  1. 经过上面的加载过程,如果 cachedAdaptiveClass 有值,就返回。没有的话,就使用字节码技术动态创建一个扩展点匹配器 AdaptiveExtensionClass 返回。(绝大部分的 SPI 扩展点匹配器 AdaptiveExtension 的创建方式) 以 Protocol.class 为例,动态创建出的扩展点适配器的类名为 Protocol$Adaptive
  2. 将获取到的扩展点匹配器实例 instance 存放到 cachedAdaptiveInstance 中并返回

#getExtension(name) 按名称 name 去获取扩展点

  1. 先按照上面 2-4 的步骤加载 SPI 扩展文件
  2. 从 cachedClasses 中获取扩展类 v (getExtensionClasses().get(name))
  3. 通过反射创建扩展点实例 instance (clazz.newInstance()),并将实例放到缓存 EXTENSION_INSTANCES 中 (EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()))
  4. injectExtension() 注入扩展点实例 instance 依赖的扩展点 【IoC】 a. 拿到 instance 类的 set 方法。set 方法需要是 public 且只有一个入参 b. 通过 set 方法的名称反推出扩展点的名字 name c. 通过 ExtensionFactory 获取到 type + name 的扩展点(objectFactory.getExtension(pt, property),pt 为 set 方法的入参 class 类型,property 为扩展点的 name) d. 通过反射注入依赖的扩展点(method.invoke(instance, object))
  5. 将所有的 cachedWrapperClasses 包装在 instance 上,并按照 4 的流程注入 wrapperClass 实例依赖的扩展点 【AOP】

附:

  • 获取 SPI 扩展点有两种方式:
  1. 先获取扩展点适配器,然后在方法调用时,扩展点适配器会动态路由到指定的扩展点去调用
  2. 通过 getExtension(name) 或者 getDefaultExtension() 方法直接获取 以 Protocol.class 为例,dubbo 在暴露一个服务的时候,会调用 Protocol#export(Invoker<T> invoker) 方法。 具体它是先获取扩展点适配器 Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(),然后再通过扩展点适配器去调用 export(Invoker<T> invoker) 方法。 而扩展点适配器 Protocol$Adaptive.class 的 export(Invoker<T> invoker) 方法则是根据 invoker 中指定的扩展点名称 extName 去调用指定扩展点的 export(Invoker<T> invoker) 方法
  • #getActivateExtension(URL url, String key, String group) 获取激活的扩展点列表
public List<T> getActivateExtension(URL url, String[] values, String group) {
    List<T> exts = new ArrayList<T>();
    List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
    // 添加框架默认激活的扩展点
    if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
        getExtensionClasses();
        for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
            String name = entry.getKey();
            Activate activate = entry.getValue();
            if (isMatchGroup(group, activate.group())) {
                T ext = getExtension(name);
                if (!names.contains(name) /* 添加默认激活的扩展点,需要排除指定激活的 */
                        && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
                        && isActive(activate, url)) {
                    exts.add(ext);
                }
            }
        }
        Collections.sort(exts, ActivateComparator.COMPARATOR);
    }

    // 添加 url 中指定激活的扩展点
    List<T> usrs = new ArrayList<T>();
    for (int i = 0; i < names.size(); i++) {
        String name = names.get(i);
        if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
                && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
            if (Constants.DEFAULT_KEY.equals(name)) {
                if (!usrs.isEmpty()) {
                    exts.addAll(0, usrs);
                    usrs.clear();
                }
            } else {
                T ext = getExtension(name);
                usrs.add(ext);
            }
        }
    }
    if (!usrs.isEmpty()) {
        exts.addAll(usrs);
    }
    return exts;
}

参考:

@see: http://dubbo.apache.org/zh-cn/blog/introduction-to-dubbo-spi.html

@see: http://dubbo.apache.org/zh-cn/blog/introduction-to-dubbo-spi-2.html

原文地址:https://www.cnblogs.com/kevin-yuan/p/10346546.html

时间: 2024-11-05 14:37:24

【Dubbo 源码解析】02_Dubbo SPI的相关文章

Dubbo源码解析之SPI(一):扩展类的加载过程

Dubbo是一款开源的.高性能且轻量级的Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用.智能容错和负载均衡,以及服务自动注册和发现. Dubbo最早是阿里公司内部的RPC框架,于 2011 年开源,之后迅速成为国内该类开源项目的佼佼者,2018年2月,通过投票正式成为 Apache基金会孵化项目.目前宜信公司内部也有不少项目在使用Dubbo. 本系列文章通过拆解Dubbo源码,帮助大家了解Dubbo,做到知其然,并且知其所以然. 一.JDK SPI 1.1 什么是SPI? S

dubbo源码解析-zookeeper创建节点

前言 在之前dubbo源码解析-本地暴露中的前言部分提到了两道高频的面试题,其中一道dubbo中zookeeper做注册中心,如果注册中心集群都挂掉,那发布者和订阅者还能通信吗?在上周的dubbo源码解析-zookeeper连接中已经讲到,这周解析的是另一道,即服务提供者能实现失效踢出是根据什么原理? 上周就有朋友问到我,为什么我的源码解析总是偏偏要和面试题挂上钩呢?原因很简单 1.dubbo源码这么多,试问你从哪里做为切入点?也就是说该从哪里看起?所以以面试题为切入点,你可以理解为我是在回答"

【Dubbo 源码解析】03_Dubbo Protocol&amp;Filter

Protocol & Filter Dubbo 服务暴露和服务引用都是通过的 com.alibaba.dubbo.rpc.Protocol 来实现的.它是一个 SPI 扩展. @SPI("dubbo") public interface Protocol { int getDefaultPort(); @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcExceptio

【Dubbo 源码解析】06_Dubbo 服务调用

Dubbo 服务调用 根据上图,可以看出,服务调用过程为: Consumer 端的 Proxy 调用 Cluster 层选择集群中的某一个 Invoker(负载均衡) Invoker 最终会调用 Protocol 层进行 RPC 通讯(netty,tcp 长连接),将服务调用信息和配置信息进行传递 Provider 端 Protocol 层接收到服务调用信息后,最终会调用真实的服务实现 Consumer 端调用过程 通过前面 Dubbo 服务发现&引用 的学习,我们知道,Consumer 端的调

【Dubbo 源码解析】07_Dubbo 重试机制

Dubbo 重试机制 通过前面 Dubbo 服务发现&引用 的分析,我们知道,Dubbo 的重试机制是通过 com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker 来实现的: public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcExce

Dubbo源码1(SPI)

SPI: http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577159 Dubbo的SPI目的:获取一个指定实现类的对象,ExtensionLoader.getExtension(String name) p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Monaco } 实现路径: getExtensionLoader(Class<T> type) 就是为该接口new 一

【Dubbo 源码解析】08_Dubbo与Spring结合

Dubbo 与 Spring 结合 基于 dubbo.jar 内的 META-INF/spring.handlers 配置,Spring 在遇到 dubbo 名称空间时,会回调 DubboNamespaceHandler. 所有 dubbo 的标签,都统一用 DubboBeanDefinitionParser 进行解析,基于一对一属性映射,将 XML 标签解析为 Bean 对象. 在 ServiceConfig.export() 或 ReferenceConfig.get() 初始化时,将 Be

dubbo源码阅读之SPI

dubbo SPI SPI,全程Service Provider interface, java中的一种借口扩展机制,将借口的实现类注明在配置文件中,程序在运行时通过扫描这些配置文件从而获取全部的实现类. java 原生的spi将借口的实现类信息放在META-INF/services/文件中.spi被广泛应用于各种框架及中间件中,用以提升框架的扩展性. dubbo也使用spi来提供各种扩展,但是dubbo并未使用java原生的spi,而是实现了自己的spi机制.dubbo的spi实现Extens

【Dubbo 源码解析】01_Dubbo 设计简介

Dubbo 设计简介 Dubbo 采用 Microkernel + Plugin (微内核 + 插件)模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换. Dubbo 的核心领域模型 Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理. Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代