乐趣区

Dubbo-源码解读

《Dubbo 源码解读》

傻瓜源码 - 内容简介

傻瓜源码 - 内容简介
????【职场经验】(持续更新)
精编短文:如何成为值钱的 Java 开发 - 指南

如何日常学习、如何书写简历、引导面试官、系统准备面试、选择 offer、提高绩效、晋升 TeamLeader…..
????【源码解读】(持续更新) <br/>1. 源码选材:Java 架构师必须掌握的所有框架和类库源码<br/>2. 内容大纲:按照“企业应用 Demo”讲解执行源码:总纲“阅读指南”、第一章“源码基础”、第二章“相关 Java 基础”、第三章“白话讲源码”、第四章“代码解读”、第五章“设计模式”、第六章“附录 - 面试习题、相关 JDK 方法、中文注释可运行源码项目”
3. 读后问题:粉丝群答疑解惑
已收录:HashMap、ReentrantLock、ThreadPoolExecutor、《Spring 源码解读》、《Dubbo 源码解读》…..
????【面试题集】(持续更新)<br/>1. 面试题选材:Java 面试常问的所有面试题和必会知识点 <br/>2. 内容大纲:第一部分”注意事项“、第二部分“面试题解读”(包括:”面试题“、”答案“、”答案详解“、“实际开发解说”)
3. 深度 / 广度:面试题集中的答案和答案详解,都是对齐一般面试要求的深度和广度
4. 读后问题:粉丝群答疑解惑
已收录:Java 基础面试题集、Java 并发面试题集、JVM 面试题集、数据库 (Mysql) 面试题集、缓存 (Redis) 面试题集 …..
????【粉丝群】(持续更新) <br/>收录:阿里、字节跳动、京东、小米、美团、哔哩哔哩等大厂内推
???? 作者介绍:Spring 系源码贡献者、世界五百强互联网公司、TeamLeader、Github 开源产品作者
???? 作者微信:wowangle03(企业内推联系我)

  加入我的粉丝社群,阅读更多内容。从学习到面试,从面试到工作,从 coder 到 TeamLeader,每天给你答疑解惑,还能有第二份收入!

第 1 章 阅读指南

  • 本书基于 Dubbo 2.6.7 版本。
  • 本书根据“企业应用 Demo”解读源码。
  • 本书建议分为两个学习阶段,掌握了第一阶段,再进行第二阶段;

    • 第一阶段,理解章节“源码解读”前的所有内容。即掌握 IT 技能:熟悉 Dubbo 原理。
    • 第二阶段,理解章节“源码解读”(包括源码解读)之后的内容。即掌握 IT 技能:精通 Dubbo 源码。
  • 建议按照本书内容顺序阅读(内容前后顺序存在依赖关系)。
  • 阅读本书的过程中如果遇到问题,记下来,后面不远的地方肯定有解答。
  • 阅读章节“源码解读”时,建议获得中文注释源码项目配合本书,Debug 进行阅读学习。
  • 源码项目中的注释含义;

    • ”企业应用 Demo“的启动代码在源码中,会标注“// Dubbo Demo”;
    • 在源码中的不易定位到的主线源码,会标注“// tofix 主线”;
  • 以下注释的源码,暂时不深入讲解:

    • 在执行“企业应用 Demo”过程中,没有被执行到的源码(由于遍历空集合、if 判断),会标注“/* Demo 不涉及 /”;
    • 从头到尾都是空的变量(包括不包含元素的集合),会标注“/* 空变量 /”;
    • 有值的变量,但“企业应用 Demo”运行过程中没有使用到该变量,会标注”/* 无用逻辑 /“;
    • 不是核心逻辑,并且不影响源码理解,会标注”/* 非主要逻辑 /“;
    • 锁、异常处理逻辑、非空校验、日志打印没有标注注释;
    • 待进一步解读的源码,会标注”// tofix“。

第 2 章 Dubbo 简介

  Dubbo 简单来讲,就是通过简单的配置,可以使调用远程方法像调用本地方法一样的分布式服务框架。

第 3 章 Dubbo 实战

  Dubbo 提供了两种使用方式:API + 注解、XML(properties 只能用作配置,指定服务的发布 / 消费必须通过这二种方式的其中一种);官方推荐使用 XML 的方式进行实现,所以本书目前只讲解关于 XML 的使用方式。

3.1 企业应用 Demo

3.1.1 服务提供者

代码示例 1 pom.xml 引入 maven 依赖

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.7</version>
        </dependency>

代码示例 2 在 applicationContext.xml 中引入 dubbo-demo-provider.xml 配置文件

<import resource="classpath:dubbo-demo-provider.xml"/>

代码示例 3 在 dubbo-demo-provider.xml 配置提供者信息

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <!-- 用于在运维平台 (dubbo-admin/dubbo-monitor) 上标识服务与应用的关系,以便在发现问题时,运维人员能够找到服务对应负责人;必须配置; 对应源码中的 ApplicationConfig 类 -->
    <!-- name 表示当前程序的应用名(必选),owner 表示发布服务的应用责任人(可选),organization 表示团队名(可选)-->
    <dubbo:application name="demo-provider"  owner="leaderName1,leaderName2" organization="teamName"/>

    <!-- 用于声明注册服务的注册中心,必须配置;对应源码中的 RegistryConfigConfig 类  -->
    <!-- address 表示注册地址: 端口,同一个注册中心集群的多个地址用”,“分隔(必填); protocol 表示注册服务的协议(选填,默认”dubbo“);-->
    
    <!-- 使用 Zookeeper 注册中心,默认使用”Curator“客户端,集群中的服务器列表顺序不重要,”Curator“客户端会连接列表中的服务器地址 -->
    <!-- 扩展 1:registry 还可以直接配置成:<dubbo:registry zookeeper://10.20.153.10:2181?backup=10.20.153.11:2181,10.20.153.12:2181/> -->
    <!-- 扩展 2:多个不同的注册中心集群再用“|”或者“;”分隔, 或者直接配置多个 <dubbo:registry/> -->
    <dubbo:registry address="10.20.153.10:2181,10.20.153.11:2181,10.20.153.12:2181" protocol="zookeeper"/>

    <!-- 用于声明提供者暴露服务的协议,可选配置;对应源码中的 ProtocolConfig 类(Dubbo 默认自动创建)-->
    <!-- name 表示暴露服务的协议(可选,默认”dubbo“),port 表示暴露服务的端口(可选,默认”20880“),accesslog 表示是否打印请求日志(可选,默认”false“)-->
    <dubbo:protocol name="dubbo" port="20880" accesslog="true"/>

    <!-- 用于将一个服务实现类配置为 <bean/>,交给 Spring 托管和实例化 -->
    <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>

    <!-- 用于声明一个暴露的服务接口;对应源码中的 ServiceConfig 类 -->
    <!-- interface 标识暴露服务的接口(必须配置),ref 标识暴露接口的实现(必须配置),group 标识服务分组(可选),version 标识服务版本(可选),timeout 标识消费者调用本服务的超时时间(以毫秒为单位)(可选,默认 1000ms),retries 表示消费者调用本服务超时重试的次数(可选,默认为 2)-->
    <!-- `group`:可以用来区分一个接口的多个实现,与对应消费者的 group 必须一致;在企业中一般仅用来标识服务;建议使用,当未来出现需要区分一个接口多个实现的场景时,容易做修改(易扩展) -->
    <!-- `version`:发布的服务版本,与对应消费者的 version 必须一致;可以用于标识一个接口实现不兼容升级的情况,但在企业中一般只使用 1.0 版本,仅用来标识服务;建议使用,当未来出现一个接口实现不兼容升级的场景时,容易做修改(易扩展) -->
    <!-- `timeout`:指的是消费者调用提供者发布服务的超时时间,默认为 1000ms,建议使用,因为服务的提供者肯定是最清楚当前服务的合理超时时间的;(提供者配置和消费者配置的优先级(谁覆盖谁),留到消费者再讲,现在可以简单理解为消费者会覆盖提供者的配置)-->
    <!-- `retries`:指的是消费者调用提供者发布服务超时后的重试次数,默认为 2 次;建议使用,因为服务的提供者肯定是最清楚当前服务是否允许重试的;(提供者配置和消费者配置的优先级(谁覆盖谁),留到消费者再讲,现在可以简单理解为消费者会覆盖提供者的配置)-->
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"
                   group="testGroup" version="1.0" timeout="3000"/>

</beans>

3.2 配置优先级

​ 不管是暴露服务还是消费服务,Dubbo 都会根据优先级对配置信息进行处理。即当优先级高的配置方式配置了”dubbo.application.name“参数,优先级相比低的配置方式也同样配置了”dubbo.application.name“参数,则会优先使用配置方式优先级高的配置;如果优先级高的没有配置”dubbo.application.name“,而优先级相比低的配置方式配置了”dubbo.application.name“参数,则会使用优先级相对低的配置方式的参数。

  Dubbo 中一共有四种配置方式,优先级排序如下:

  1. 通过 -D 传递配置参数给 JVM(例如:在 tomcat/bin 下的 catalina.sh,加入一行 JAVA_OPTS=”-DconfName=confValue”),优先级最高,但通常不会被使用;
  2. 外部化配置(使用外部系统,例如:Apollo、Nacos、dubbo-admin、incubator-dubbo-ops 等,在外部平台对 Dubbo 进行配置),优先级次之;
  3. 通过 XML 或者 API + 注解的方式配置,优先级次次之。(实际上不管是使用 XML 或者是 API + 注解的方式,底层都是新建 Dubbo API 对象,并通过调用 API 的 set 方法进行的赋值);
  4. Dubbo 配置文件 dubbo.properties,优先级最低。

3.3 更多配置项

  • 如果想要在项目中应用 Dubbo,并且需要的配置项多于当前示例,可以参考官网地址:schema 配置参数手册。
  • 官方网站收录的可配置参数,对于当前版本来讲,不一定就是所有可配置项;如果想要知道并使用更多配置项,可以参照以下方式在源码中寻找对应的配置项(实现逻辑见章节“appendProperties(AbstractConfig config)”);

    1. 首先,在 Dubbo 中找到配置的实体类源码;
    2. 然后,搜索要配置属性的 set 方法(包括继承来的),如果 set 方法以 public 修饰,并且只有一个参数,并且参数是基本类型、基本类型的包装类、String、Object 类,就代表能够进行配置。比如:想要配置 dubbo:application(对应 ApplicationConfig 应用信息配置类)的 qosPort 参数,对应配置项就是:“dubbo.”(固定常量)+“application”(ApplicationConfig 去掉 Config 后,剩余的部分转小写)+“qos.port”(qosPort 根据大写字母拆分,然后用“.”拼接,然后转小写),即“dubbo.application.qos.port”。

第 4 章 相关 Java 基础

4.1 Zookeeper

  1. 介绍

  Zookeeper 是一个树形的目录服务,是 Dubbo 推荐使用的注册中心。为了让消费者能够从注册中心拿到提供者的发布信息,Dubbo 会把自己发布的提供者服务信息注册到注册中心(向注册中心注册服务的过程,实质上就是在 Zookeeper 上创建对应的目录节点),从而让消费者根据注册中心上的提供者服务信息,发起对提供者的远程调用。

  目录节点分为持久节点和临时节点:

  • 持久节点:就是不会被自动删除的节点。
  • 临时节点:就是满足条件就会被自动删除的节点。删除条件:当在会话超时(session_timeout)时间内,Zookeeper 服务端没有接收到客户端定时发送的心跳(heart_beat),就会将临时节点删除掉。

  Dubbo 提供者暴露的服务信息就是以临时节点的方式存储在 Zookeeper,如果粉丝所在企业没有搭建 dubbo-admin 等管理控制台的话,可以通过查询 Zookeeper 节点是否存在的方式来判断提供者是否正常启动。

  2. 会话超时(session_timeout)

  在 ZooKeeper 中,客户端(Dubbo 提供者)和服务端(Zookeeper)建立连接后,会话随之建立,服务端和客户端之间会维持一个 TCP 长连接(TCP 长连接就是指客户端和服务端之间,发送完数据包之后,如果再没有数据包发送,就会互相发送检测包,来维持此连接)。为了让服务端确定客户端正常连接,客户端会定时向 Zookeeper 服务端发送 heart_beat,服务端接收到 heart_beat 后,会重新计算 session_timeout 时间。

  如果在 session_timeout(Dubbo 默认 6000 ms)时间内,Zookeeper 服务端没有接收到心跳(可能因为网络问题或者客户端宕掉了),则会删除客户端在 Zookeeper 服务端对应的目录。

  如果客户端没有指定会话超时时间的话,session_timeout 等于 minSessionTimeout(如果没有配置 minSessionTimeout,minSessionTimeout = tickTime * 2(tickTime 在 Zookeeper 服务端配置文件中默认为 2000 ms));

  如果客户端指定了会话超时时间,则必须在服务端配置的 minSessionTimeOut(若没配置 minSessionTimeOut,minSessionTimeOut = tickTime 2)和 maxSessionTimeOut(若没配置 maxSessionTimeOut,maxSessionTimeOut = tickTime 20 ) 闭区间里,如果跨越上下界值,则取跨越的上界或下界值。

4.2 匿名内部类

代码示例 1 抽象类

public abstract class Person {public abstract void eat();
}

代码示例 2 实例化抽象类

public class Demo {public static void main(String[] args) {// 这种实例化抽象类或者接口,并且能够指定抽象方法实现的方式,是 JavaC 编译器通过将 new Persion{...} 编译成匿名内部类的方式实现的
        // 在 JavaC 编译器编译的时候,Persion.java 和 Demo.java 文件会编译成 Persion.class 和 Demo.class 文件
        // 还会多生成一个 Demo$1.class 文件,这个就是匿名内部类对应的 class 文件,这个文件名不是 Persion 真实的名字,所以称之为“匿名”;生成字节码的方式是通过内部类的方式生成的;所以叫做匿名内部类
        Person p = new Person() {
            @Override
            public void eat() {System.out.println("eat something");
            }
        };
        p.eat();
        
        Person p = () -> {System.out.println("eat something");
        };
        p.eat();}
}

  匿名内部类适用于在多个不同的调用场合,抽象方法会有不同实现逻辑的场景。反过来想,如果不使用匿名内部类,想要在不同调用场合,执行不同实现逻辑,要不就是在类中为每个场合增加一个方法、要不就是为每个场合定义一个实现了抽象类或接口的类,然后在实现类的方法里实现对应逻辑。这样比较,使用创建匿名内部类的方式是不是大大简化了代码呢!

4.3 正则表达式

代码示例 正则表达式

    // 示例 1
    public static void main(String args[]) {
        
        String line = "This order was placed for QT3000! OK?";
        
        // * 表示匹配前面的子表达式零次或多次
        // + 表示匹配前面的子表达式一次或多次
        // \D* 表示零次或多次匹配非数字
        // \d+ 表示一次或多次匹配数字
        // .* 表示单个字符匹配任意次
        // \ 将下一个字符标记为特殊字符,例如:\\
        
        // 正则表达式
        String regex = "(\\D*)(\\d+)(.*)";
        // Pattern 对象是一个正则表达式的编译表示
        Pattern r = Pattern.compile(regex);
        // Matcher 对象是对输入字符串进行解释和匹配操作的引擎
        Matcher m = r.matcher(line);
        // find 表示查找与该模式匹配的输入字符串中的下一个子字符串
        if (m.find()) {
            /**
             * group 称之为捕获组,首先通过对传入的正则表达式进行分组,然后从左至右通过组的下角标获得匹配到的字符
             * 比如:正则表达式((A)(B(C))),调用 group 方法的效果如下
             * group(0):((A)(B(C))),代表整个表达式
             * group(1):(A)
             * group(2):(B(C))
             * group(3):(C)
             */

            // m.group(0) 表示匹配整个 regex 表达式 (\\D*)(\\d+)(.*)
            // 打印结果:This order was placed for QT3000! OK?
            System.out.println(m.group(0));
            // m.group(1) 表示匹配 regex 表达式 (\\D*)
            // 打印结果:This order was placed for QT
            System.out.println(m.group(1));
            // m.group(2)表示匹配 regex 表达式 (\\d+)
            // 打印结果:3000
            System.out.println(m.group(2));
            // m.group(3)表示匹配 regex 表达式 (.*)
            // 打印结果:! OK?
            System.out.println(m.group(3));
        }
    }

    // 示例 2
    public static void main(String[] args) throws IOException {
        // \s 匹配任何空白字符
        // [|;] 字符集, 匹配 [] 中的任一字符。String[] addresses1 = Pattern
                .compile("\\s*[|;]+\\s*").split("abc|efg;hig|;klmn");
        // 打印结果:["abc","efg","hig","klmn"]
        System.out.println(JSONObject.toJSONString(addresses1));

4.4 自定义注解

代码示例 1 自定义注解

    /**
     * 自定义注解的定义
     */
    // 注解生效的阶段,比如编译、运行等;一般都是 RUNTIME
    @Retention(RetentionPolicy.RUNTIME)
    // 表示可以用在的地方,比如方法、类等等;不标注则可以用在任何程序元素上
    @Target({ElementType.TYPE, ElementType.METHOD})
    public @interface Custom {
        // 定义的注解属性
        String value() default "";}

代码示例 2 在类上标注自定义注解

@Custom("配置的值")
public class Demo {...}

代码示例 3 获取类上配置的自定义注解

    public static void main(String[] args) {
        // 获取在 Demo 上的 @Custom 注解对象
        Custom custom = Demo.class.getAnnotation(Custom.class);
        // 获取 @Custom 注解中的 value 值
        String value = custom.value();
        System.out.println(value);
    }

4.5 反射

​ 在 JVM 运行过程中,对于任意一个类,都能通过方法获得这个类的属性、和方法。这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。

第 5 章 源码基础

5.1 导读

   1. 对象种类

  执行“企业应用 Demo”时,在源码中共用到三种对象:Pojo、Bo、DTO;

  • Pojo(plian ordinary java object):仅包含属性以及属性的 get、set、add、remove、is、has 方法的简单对象
  • Bo(business object):就是封装着业务逻辑的对象
  • DTO(Data Transfer Object):数据传输对象

5.2 ApplicationConfig

  应用信息配置类(Pojo)。每个 ServiceConfig 都共同拥有同一个 ApplicationConfig 对象,是从 Spring 容器中获取的单例 Bean 实例。

代码示例 重要成员变量

public class ApplicationConfig extends AbstractConfig {
    
    // name 对应 <dubbo:application name="demo-provider"/> 表示提供者的应用程序名
    private String name;
    
    // owner 对应 <dubbo:application owner="leaderName1,leaderName2" /> 表示发布服务的应用责任人
    private String owner;
    
    // organization 对应 <dubbo:application organization="teamName"/> 表示团队名
    private String organization;

5.3 ProtocolConfig

  提供者暴露服务的协议配置类(Pojo)。每个 ServiceConfig 都以集合的方式共同拥有相同的 ProtocolConfig 对象,是从 Spring 容器中获取的单例 Bean 实例。

代码示例 重要成员变量

public class ProtocolConfig extends AbstractConfig {

    //  name  对应 <dubbo:protocol name="dubbo"/> 表示配置的暴露服务协议
    private String name;

    // port  对应 <dubbo:protocol port="20880"/> 表示配置的暴露服务端口
    private Integer port;

    // accesslog 对应 <dubbo:protocol accesslog="true"/> 表示配置的是否打印请求日志
    private String accesslog;

5.4 RegistryConfig

  注册中心配置类(Pojo)。每个 ServiceConfig 都以集合的方式共同拥有相同的 ProtocolConfig 对象,是从 Spring 容器中获取的单例 Bean 实例。

代码示例 重要成员变量

public class RegistryConfig extends AbstractConfig {

    // address 对应 <dubbo:registry address="10.20.153.10:2181,10.20.153.11:2181,10.20.153.12:2181"/> 表示配置的注册中心地址
    private String address;

    // protocol 对应 <dubbo:registry protocol="zookeeper"/> 表示配置的注册服务协议
    private String protocol;

5.5 URL

​ URL 类(DTO),是 Dubbo 中大部分方法参数列表中的参数或参数中的属性,用于方法之间传递参数;这样做的好处是极易扩展,如果想要修改参数列表中的传递参数,不需要改变方法的参数列表,只需要修改 URL 类即可,并且大部分情况只需要修改 URL 类中的 Map<String, String> parameters 属性值来完成修改参数列表的动作。

代码示例 重要成员变量

public final class URL implements Serializable {
    
    // protocol 表示协议。用于在运行过程中,Dubbo 可以根据这个向后传递的字段值,找到(自适应到)对应对象(扩展类);值可能是 <dubbo:registry protocol="zookeeper"/> 中的 "zookeeper",可能是 <dubbo:protocol name="dubbo" /> 中的 "dubbo"。详见章节“自适应”private final String protocol;

    // IP 表示向后传递的 IP 地址。可能是本地 IP;可能是注册中心的 IP 地址(如果是集群,就是配置集群时的第一个 IP 地址)private final String host;
    
    // prot 表示用于向后传递的端口号。可能是注册中心 <dubbo:registry/> 的注册端口,如:2181(如果是集群,就是配置集群时的第一个 IP 对应的端口号);可能是提供者发布远程服务 <dubbo:protocol/> 的端口,如:20880
    private final int port;
    
    // path 用于标记正在处理的服务接口全局限定名。如:"com.alibaba.dubbo.demo.DemoService"、"com.alibaba.dubbo.registry.RegistryService"
    private final String path;
    
    // parameters 用于存储使用者的配置和代码运行过程中需要传递的参数,比如{"application"->"demo-provider","timestamp" -> "1573636770451","pid" -> "4324","dubbo" -> "2.0.2","backup" -> "10.20.153.11:2181,10.20.153.12:2181"}
    private final Map<String, String> parameters;

5.5.1 @Parameter

  URL 使用 Map<String, String> 类型的 parameters 变量来传递大部分参数,传递的参数当然也包括 Config 配置类的配置变量。但是直接使用类的成员变量放在 parameters 是有问题的,比如:多个类有相同属性名、驼峰命名不适合作为参数传递、类中并不是所有的属性都需要向后传递的。

  所以为了解决这一系列的问题,就需要标识出这些成员属性是否应该放在 parameters 里、放在 parameters 中的键名是怎样的,等等;但是硬编码很明显不是好的解决办法,所以 Dubbo 就自定义注解 @Parameter 来标识变量,在代码中如果检测到变量标识了该注解,就根据注解的配置进行操作。

代码示例 重要成员变量

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Parameter {

    // key 标识属性在 parameters 里使用的 key 值
    String key() default "";

    // required 表示标识属性是否必填;如果为 true,但是属性中没有值,则会抛出异常
    boolean required() default false;

    // excluded 表示是否需要放在 parameters 里
    boolean excluded() default false;

    // escaped 表示是否需要 UTF-8 编码后,再放入 parameters
    boolean escaped() default false;

    // append 表示属性值是否要拼接后,再放入 parameters 
    boolean append() default false;

5.6 Dubbo SpI

  Dubbo SPI,又称服务发现机制,也叫扩展点机制。本质就是将接口实现类的简称以及接口实现类的全限定名,通过”key = value“的形式配置在以”接口全局限定名“命名的扩展配置文件中。扩展配置文件扫描路径默认有三个地方:META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/。

  在 SPI 中,涉及到的接口叫做扩展点,接口的实现类叫做扩展类,接口实现类的简称叫做扩展名(扩展名是 Dubbo 设计者自定义的)。

  Dubbo 中参与运行逻辑的所有核心实例都是基于扩展点机制实现的。当 Dubbo 内部想要使用某个扩展点的扩展类时,都是根据扩展名在相应扩展配置文件中找对应扩展类的。

  1. 扩展点好处

  当 Dubbo 的开发人员因为重构、升级 Dubbo 等原因,想要增加或者替换接口实现时,只需要增加接口实现类作为扩展类、在扩展配置文件中增加一行“扩展名 = 扩展类全局限定名”、在指定扩展名的地方修改扩展名即可(一般在 dubbo-demo-provider.xml 上指定和 @SPI 上配置默认扩展名);不需要改动原代码,易于扩展。

  2. @SPI 注解

  为了标识接口是扩展点,都会标注自定义注解 @SPI;其中 @SPI 中的 value 值就是标识这个接口默认扩展类的扩展名的(如果在 Dubbo 执行时,没有在 dubbo-demo-provider.xml 上指定扩展名,就使用默认扩展名对应的扩展类实例)。

代码示例 重要成员变量

/**
 * 用于标注扩展点
 * 
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    // value 表示默认扩展类的扩展名
    String value() default "";}

  3. 简单代码示例

代码示例 3-1 扩展点

@SPI
public interface Robot {void sayHello();
}

代码示例 3-2 扩展类

public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {System.out.println("Hello, I am Bumblebee.");
    }
}

代码示例 3-3 扩展配置文件

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

代码示例 3-4 执行代码

  Dubbo SPI 的处理逻辑都封装在了 ExtensionLoader 类(扩展类加载器)中;通过 ExtensionLoader,我们可以根据指定的扩展名加载对应的扩展类。

public class DubboSPITest {

    @Test
    public void sayHello() throws Exception {
        // 初始化指定扩展点的扩展类加载器
        ExtensionLoader<Robot> extensionLoader = 
            ExtensionLoader.getExtensionLoader(Robot.class);
        
        // 加载器根据扩展名 "optimusPrime" 获取相应扩展类
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        // 执行扩展类的方法
        optimusPrime.sayHello();
        
        // 加载器根据 "bumblebee" 扩展名获取相应扩展类
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        // 执行扩展类的方法
        bumblebee.sayHello();}
}

  测试结果如下:

  4. Dubbo 代码示例

代码示例 4-1 Dubbo 配置

<!-- 使用扩展名“dubbo”对应的扩展类,来声明提供者暴露服务的协议 -->
<dubbo:protocol name="dubbo" />

代码示例 4-2 Dubbo 扩展配置

# Dubbo 在 classPath 下的 `META-INF/dubbo/internal` 路径下放置了以 `org.apache.dubbo.rpc.Protocol` 命名的扩展配置文件,配置扩展名”dubbo“对应的扩展类”com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol“dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol

代码示例 4-3 扩展点

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

代码示例 4-4 扩展类

package com.alibaba.dubbo.rpc.protocol.dubbo;
 
import org.apache.dubbo.rpc.Protocol;
 
public class DubboProtocol extends Protocol {...}

5.6.1 扩展点特性

  Dubbo SPI 共有四种扩展点特性:自适用、自动包装、自动装载、自动激活;对应三种扩展类:普通扩展类、包装扩展类、自适应扩展类。

5.6.1.1 自适应

  当 Dubbo 调用指定自适应扩展类的方法时,根据 URL 传递的参数,可以动态调用到目标扩展类(普通扩展类)的同名方法上。自适应就是从传入 URL 到调用到目标扩展类同名方法的这个过程。

  1. 自适应扩展类

  当 Dubbo 想要获取某个扩展点的自适应扩展类实例时,首先会调用 getExtensionLoader(Class<T> type) 方法,传入扩展点来获得指定扩展点的扩展类加载器,然后调用扩展类加载器的 getAdaptiveExtension() 方法,来获得一个封装着自适应逻辑的自适应扩展类。Dubbo 中大部分参与运行逻辑的扩展类实例对象都是自适应扩展类实例。每个扩展点最多只有一个自适应扩展类。

  自适应扩展类有两种实现方式:

  第一种,自适应扩展类命名以 Adaptive 开头,并且肯定在类上标注了 @Adaptive 注解,当 Dubbo 想要获取某个扩展点的自适应扩展类时,就默认获得这种自适应扩展类,这种扩展类中在实现中编写了自适应逻辑;目前为止,Dubbo 只有两个类是这种方式:AdaptiveExtensionFactory(详见“扩展类加载器 ExtensionLoader”)和 AdaptiveCompiler(详见“AdaptiveCompiler 编译器”)。

  第二种,类上没有标注 @Adaptive 注解;当 Dubbo 在运行过程中,没有找到第一种自适应扩展类时,则会根据扩展点生成一个封装自适应逻辑的自适应扩展类字节码(字符串形式);然后默认使用 Javassist 编译器,根据字节码字符串生成 Class 对象,最后实例化成真实对象。

  大部分场景都是使用第二种方式,并且这种实现方式要求扩展点必须存在标注了 @Adaptive 注解的方法,因为要生成自适应扩展类,不仅需要通过 @Adaptive 注解知道哪些方法要应用自适应特性,还要根据 @Adaptive 注解配置的内容,知道怎样生成自适应逻辑。

  2. 基于 Transporter 扩展点的 bind 方法生成的第二种自适应扩展类 - 代码样例

代码示例 2-1 @Adaptive 注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {String[] value() default {};

}

代码示例 2-2 Transporter 扩展点

@SPI("netty")
public interface Transporter {@Adaptive({"server", "transport"})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;
    
    ...

代码示例 2-2 生成的 Transporter 自适应扩展类字节码

package com.alibaba.dubbo.remoting;

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

public class Transporter$Adaptive implements Transporter {public Server bind(URL paramURL, ChannelHandler paramChannelHandler) throws RemotingException {if (paramURL == null)
      throw new IllegalArgumentException("url == null"); 
    URL uRL = paramURL;
    String str = uRL.getParameter("server", uRL.getParameter("transporter", "netty"));
    if (str == null)
      throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + uRL.toString() + ") use keys([server, transporter])"); 
    Transporter transporter = (Transporter)ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(str);
    return transporter.bind(paramURL, paramChannelHandler);
  }
}

  3. 第二种自适应扩展类 - 自适应逻辑

  1. Dubbo 将配在 dubbo-demo-provider.xml 里的值组装成 URL 对象,作为参数或者参数中的属性传递到自适应扩展类 Transporter$Adaptive 的 bind 方法,进而根据参数,调用实现了 Transporter 的目标扩展类的 bind 方法;
  2. bind 方法在调用目标方法之前,会按照 @Adaptive 中配置的 value 值顺序,从 URL 中的 parameters 找”server“对应的值;如果没有找到,再找“transport”对应的值;如果也没有找到,则直接使用 Transporter 扩展点中 @SPI(“netty”) 里的 “netty” 作为默认扩展名(如果使用者在 dubbo-demo-provider.xml 配置文件中配置了 <dubbo:protocol server=”” > 或者 <dubbo:protocol transporter=”” >,则能够在 URL 中的 parameters 找到”server“或者“transport”对应的值,作为扩展名)
  3. bind 方法的最后调用了 getExtensionLoader(Class<T> type) 方法,传入 Transporter 的 Class 对象,获得 Transporter 扩展点的扩展类加载器,然后调用扩展类加载器的 getExtension(String name) 方法,传入上一步找到的扩展名,获得对应的普通扩展类(或者 被包装扩展类包装的普通扩展类),执行目标方法。

  4. 第二种自适应扩展类 - 其它样例

  除了“2. 第二种自适应扩展类 - 代码样例”以外,还有其它情况:

  1. 假如注解 @Adaptive 设置的 value 值是“Protocol”,那么在生成第二种自适应扩展类字节码的时候,就会将代码 String str = uRL.getParameter(“server”, uRL.getParameter(“transporter”, “netty”)) 替代为 String extName = (url.getProtocol() == null ? “dubbo” : url.getProtocol()),旨将 URL 中 protocol 的字段值作为扩展名找到对应普通扩展类。
  2. 其它情况,暂时省略。

代码示例 4-1 Protocol 扩展点

@SPI("dubbo")
public interface Protocol {int getDefaultPort();

    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

代码示例 4-2 生成的 Protocol 自适应扩展类字节码

package com.alibaba.dubbo.rpc;

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

public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {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);
    }
5.6.1.2 自动包装

  自动包装就是:包装扩展类采用了封装了实现同一扩展点的普通扩展类调用,并在调用前后添加了一些公共逻辑(装饰者模式);

  1. 包装扩展类

  当 Dubbo 将 URL 参数传入到自适应方法之后,自适应方法的内部逻辑会判断如果还有其它包装扩展类实现了同一扩展点,则先会调用包装扩展类的同名方法,最终才会调用到目标扩展类(普通扩展类)。

  在 Dubbo 中,包装扩展类命名都是以 Wrapper 为结尾,并且实现了扩展点,并持有被包装的普通扩展类引用。(一定有以扩展点为唯一参数的构造函数,用于通过构造函数传入要包装的目标普通扩展类)

  2. 代码示例

代码示例 2-1 扩展点

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

代码示例 2-2 普通扩展类

public class RegistryProtocol implements Protocol {
 
    // 目标方法
    public void refer() {// 真实操作}
    
}

代码示例 2-3 包装扩展类

public class ProtocolFilterWrapper implements Protocol {
    Protocol protocol;
 
    public ProtocolFilterWrapper(Protocol protocol) {this.protocol = protocol;}
 
    // 代理目标方法
    public void refer() {
        // 公共逻辑
        impl.refer();
        // 公共逻辑
    }
 
}
5.6.1.3 自动装配

​ 自动装配是指当加载普通扩展类时,如果发现其成员变量存在其它的扩展点类型的,并且存在对应 set 方法的(仅有一个参数、访问级别为 public),ExtensionLoader 会利用反射和扩展点机制,自动注入该扩展点的自适应扩展类实例,也称为 Dubbo IOC 机制。

5.6.1.4 自动激活

  通俗一点解释就是:根据调用者的请求参数,返回(激活)对应的普通扩展类集合给调用者。

  本质就是:扩展类加载器根据传入的过滤条件参数以及 URL 参数,返回给调用者实现了指定扩展点并标注 @Activate 注解的普通扩展类实例集合(还会按照 @Activate 注解上指定的顺序信息进行排序)。使用地方不多。

5.6.2 编译器

  Dubbo 中有三种代码编译器,分别有 JDK 编译器、Javassist 编译器、AdaptiveCompiler 编译器,这几种编译器都是实现了 Compiler 扩展点的扩展类。编译器可以做到将 Class 字节码字符串编译为 Class 对象,在 Dubbo 中主要用在自适应扩展类的生成和 Invoker 的生成。

  JDK 编译器、Javassist 编译器各自封装了 JDK 和 Javassist 字节码操作类库;而 AdaptiveCompiler 则是封装了调用 JDK 编译器还是 Javassist 编译器的自适应逻辑。

5.6.2.1 AdaptiveCompiler 编译器

  1. 原理

  如果使用者指定了 JDK 编译器,则使用使用者指定的;如果没有,则使用在 Compiler 扩展点上 @SPI(“javassist”) 指定的默认扩展类:Javassist 编译器。(Javassist 编译器作为默认编译器,是因为它的效率更高;JDK 反射所需的时间更多)。

5.6.2.2 Javassist 编译器

  1. 原理

  1. 根据扩展点接口信息,组装自适应扩展类 Class 字符串;
  2. 然后使用 Javassist 编译器对 Class 字符串进行编译,得到真正的 Class 对象;
  3. 最后调用 newInstance 方法,进行实例化。

  2. 代码示例

  本示例主要实现的功能是根据字节码字符串生成对象实例,用于演示 Javassist 是如何实现动态代理的。

代码示例 2-1 执行方法

public class Test {public static void main(String args[]) throws Exception {
        // 初始化 ClassPool 容器
        ClassPool classPool = new ClassPool(true);
        classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
        // ClassPool 容器构建类名是“HelloWorldClass”的 CtClass 对象,构建后保存起来,无需二次创建
        CtClass ctClass = classPool.makeClass("HelloWorldClass");
        // 创建方法 
        // $2 对应参数列表中第二个参数,依次类推 $3、$4
        CtMethod ctMethod = CtNewMethod.make("public void test(Object oneP, String twoP, String thirdP){" +
                "System.out.println($2+twoP);" +
                "}", ctClass);
        // 向 ctClass 添加方法
        ctClass.addMethod(ctMethod);
        // 生成 Class 对象
        Class aClass = ctClass.toClass();
        // 通过反射实例化 Class 对象
        Object object = aClass.newInstance();
        // 调用 test 方法
        Method m = aClass.getDeclaredMethod("test", Object.class,String.class,String.class);
        m.invoke(object, "第一参数","第二参数","第三参数");
    }
}

代码示例 2-2 生成的字节码

public class HelloWorldClass {
  // paramObject1 等参数命名是 javassist 自己重新定义的    
  public void test(Object paramObject, String paramString1, String paramString2) {System.out.println(paramString1+paramString2); }
}
5.6.2.3 JDK 编译器

  因为企业一般都在用默认编译器 Javassist,所以目前暂时不讲解 JDK 编译器。

5.6.3 扩展类加载器 ExtensionLoader

  扩展类加载器(ExtensionLoader),负责加载扩展类;每个扩展点都有自己的扩展类加载器(即扩展类加载器都是属于某一个扩展点的);

代码示例 1 重要成员变量

public class ExtensionLoader<T> {

    // type 代表扩展点 Class 对象
    private final Class<?> type;
    
    // objectFactory 表示 AdaptiveExtensionFactory 实例,在自动装配(injectExtension(T instance))时使用;负责给扩展类实例中是扩展点类型的成员变量生成自适应扩展类实例的工厂
    // 如果是 ExtensionFactory 的扩展点加载器,本属性值就为 null
    private final ExtensionFactory objectFactory;
    
    // cachedDefaultName 表示 @spi("") 中指定的默认扩展名(执行 getExtensionClasses() 时初始化的)private String cachedDefaultName;
    
    
    /**
     * 
     * 以下属性是扩展点机制相关的缓存变量,建议在阅读源码时,再参照着看
     *
     */
    
    
    /** 扩展类加载器 */
    
    // 数据结构:{扩展点 -> 对应的扩展类加载器}。当需要获取指定的扩展类加载器时,调用 getExtensionLoader(Class<T> type)方法,加载该扩展类加载器并设置到本缓存里,后续获取操作直接从缓存里取;所有扩展类加载器共用本缓存实例
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
    
    
    /** 普通扩展类 */
    
    // 数据结构:{扩展类 Class -> 普通扩展类的扩展名}。当需要加载扩展点时,调用 getExtensionClasses() 方法,立刻由扩展类加载器加载该扩展点的所有普通扩展名到本缓存里;每个扩展加载器都有自己独立的缓存;private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
    
    // 数据结构:{扩展名 -> 普通扩展类 Class}。当需要加载扩展点时,调用 getExtensionClasses() 方法,立刻由扩展类加载器加载该扩展点的所有普通扩展 class 到本缓存中;每个扩展加载器都有自己独立的缓存;// Holder 作为持有类,变量用 volatile 修饰;private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
    
    // 数据结构:{普通扩展点 Class -> 普通扩展类实例(可能被包装扩展类实例包装) }。当需要调用 createExtension(String name) 方法创建普通扩展类实例时,由扩展类加载器从 cachedClasses 中获取指定 name 的普通扩展类 class,实例化后,放到本缓存中;所有扩展类加载器共用本缓存;private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
    
    // 数据结构:{扩展名 -> 普通扩展类实例(可能被包装扩展类实例包装)}。当需要调用 getExtension(String name) 方法获取普通扩展类实例时,加载 Holder 实例,初始化缓存;然后调用 createExtension(String name) 方法,由扩展类加载器从 cachedClasses 中获取指定 name 的普通扩展类 class,实例化后,放到 Holder 里;每个扩展加载器都有自己独立的缓存;private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
    
    
    /** 自适应扩展类 */
    
    // 保存 type 的自适应扩展类 class。当需要加载扩展点时,调用 getExtensionClasses() 方法,立刻由扩展类加载器加载该扩展点的自适应扩展类 class(第一种自适应实现方式)到本缓存中;如果不存在第一种自适应实现方式,则当需要获得指定扩展点的自适应扩展类实例时,调用 getAdaptiveExtensionClass() 方法获得自适应扩展类 class(第二种自适应实现方式),由扩展类加载器加载该自适应扩展类实例到本缓存中;每个扩展加载器都有自己独立的缓存。private volatile Class<?> cachedAdaptiveClass = null;
        
    // 保存 type 的自适应扩展类实例。当需要获得指定扩展点的自适应扩展类实例时,调用 getAdaptiveExtension() 方法,由扩展类加载器从 cachedAdaptiveClass 中获取该自适应扩展类 class,实例化后,放到本缓存中,后续获取操作直接从缓存里取;每个扩展加载器都有自己独立的缓存。// Holder 作为持有类,变量用 volatile 修饰,保证可见性;private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
    
    
    /** 包装扩展类 */
    
    // 保存 type 包装扩展类 Class。当需要加载扩展点时,调用 getExtensionClasses() 方法,立刻由加载器加载该扩展点的所有包装扩展类 Class 到本缓存中;每个扩展加载器都有自己的缓存实例;private Set<Class<?>> cachedWrapperClasses= new ConcurrentHashSet<Class<?>>();
    
    
    /** 自动激活 */
    
    // 数据结构:{扩展名 -> @Activate 注解实例}。当需要加载扩展点时,调用 getExtensionClasses() 方法,立刻由加载器加载该扩展点的所有标注 @Activate 的普通扩展类的扩展名到本缓存中;每个扩展加载器都有自己的缓存实例;private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();

代码示例 2 Holder 类

/**
 * Helper Class for hold a value.
 * 帮助类持有最新值(volatile)*/
public class Holder<T> {

    private volatile T value;

    public void set(T value) {this.value = value;}

    public T get() {return value;}

}

5.7 Invoker

  接口 Invoker(Bo),有多种实现,比如:AbstractProxyInvoker,DelegateProviderMetaDataInvoker、RegistryProtocol.InvokerDelegete。中文翻译”调用者“,顾名思义,就是当消费者调用提供者发布的服务方法时,最终调用的都是这个 Invoker 的统一个方法 doInvoke,由 Invoker 去帮你调用真正的目标方法。也就是说 Dubbo 对外发布的都是这个 Invoker。

5.7.1 AbstractProxyInvoker

  1. 介绍

  AbstractProxyInvoker(Bo),抽象类,仅实现 Invoker 接口,封装了调用目标方法的逻辑;直接用于将提供者的服务本地发布。也是用于将提供者的服务远程 Netty 发布最基础的 Invoker(可以理解为用于远程发布(Netty)第一阶段的 Invoker)。

  1.1 提供者本地服务

  当一个应用既是一个服务的提供者,又是这个服务的消费者时,消费者可以不需要经过网络,直接就在本地调用提供者的服务,这就是 Dubbo 发布的本地服务。(消费者会优先消费本地服务,而不消费远程服务)

  在“企业应用 Demo”中,默认就会发布本地服务。

  现在粉丝只需要简单知道:底层就是把这个 AbstractProxyInvoker 以匿名内部类的方式存到 Map 里,消费者调用的时候,会从这个 Map 里取。

代码示例 1-1 Invoker 实现类 AbstractProxyInvoker 重要成员变量

public abstract class AbstractProxyInvoker<T> implements Invoker<T> {

    // 代理接口的实现类实例,例:DemoServiceImpl 对象
    private final T proxy;

    // 接口 Class,例:(class)DemoService
    private final Class<T> type;

    // url 对象。例:registry://10.20.153.10:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&backup=10.20.153.11:2181,10.20.153.12:2181&dubbo=2.0.2&export=dubbo%3A%2F%2F 本地 IP%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Faccesslog%3Dtrue%26anyhost%3Dtrue%26application%3Ddemo-provider%26bean.name%3Dcom.alibaba.dubbo.demo.DemoService%26bind.ip%3D 本地 IP%26bind.port%3D20880%26dubbo%3D2.0.2%26generic%3Dfalse%26group%3DtestGroup%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%2CsetSetterMethod%2CgetGetterMethod%2CisIsMethod%26pid%3D28756%26revision%3D1.0%26side%3Dprovider%26timeout%3D3000%26timestamp%3D1586484624885%26version%3D1.0&pid=28756&registry=zookeeper&timestamp=1586484624861
    private final URL url;
    
    protected abstract Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable;

  2. 实现逻辑

  第一步,请求获得 AbstractProxyInvoker。通过代理工厂,根据服务接口或实现信息生产一个 Invoker。(代理工厂默认使用 JavassistProxyFactory 实例)。

代码示例 2-1 获取 Invoker 的逻辑

Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url)

  第二步,生产 AbstractProxyInvoker。调用 getWrapper 方法,getWrapper 内部默认使用 Javassist 组件,根据发布的接口 / 实现信息,利用字节码技术新生成一个 wrapper 包装类,然后把这个包装类里的通用调用方法,使用匿名内部类的方式传入到实现 Invoker 接口的 AbstractProxyInvoker 里,作为 Invoker 被调用 doInvoke 方法时的真正实现;这种实现方式使得 Invoker 能做的事非常灵活。

代码示例 2-2 生成 Invoker 的逻辑

    /**
     * 使用 Javassist 组件,动态代理发布接口,返回代理类 AbstractProxyInvoker 实例
     *
     * @param proxy 发布接口的实现类实例
     * @param type 发布接口 Class
     * @param url URL
     * @param <T>
     * @return 返回 AbstractProxyInvoker,是对目标接口的代理
     */
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // 通过 Javassist 编译器技术,将发布服务接口 / 实现的字节码再处理,生成包装类 wrapper
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);

        // 使用创建匿名内部类的方式,将 wrapper 的 invokeMethod 方法指定给 AbstractProxyInvoker#doInvoker 
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

  3. Wrapper 包装类示例

代码示例 3-1 发布的接口(包含 setter、getter、is 方法)

public interface DemoService {String sayHello(String name);

    void setSetterMethod(Object name);

    Object getGetterMethod();

    boolean isIsMethod();}

代码示例 3-2 发布接口的实现(包含 setter、getter、is 方法)

public class DemoServiceImpl implements DemoService {

    @Override
    public String sayHello(String name) {System.out.println("Hello");
        return "return Hello";
    }

    @Override
    public void setSetterMethod(Object name) { }

    @Override
    public Object getGetterMethod() {return null;}

    @Override
    public boolean isIsMethod() {return false;}

}

代码示例 3-3 生成的包装类 Wrapper0.Class

package com.alibaba.dubbo.common.bytecode;

import com.alibaba.dubbo.demo.DemoServiceImpl;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;

public class Wrapper0 extends Wrapper implements ClassGenerator.DC {
  // pns 表示属性名数组  
  public static String[] pns;
  // pts 表示 {属性名 -> 属性类型} 集合
  public static Map pts;
  // mns 表示被包装类的公有方法名集合(除从 Object 继承来的方法外)
  public static String[] mns;
  // dmns 表示被包装类的公有方法名集合(除所有继承来的方法外)
  public static String[] dmns;
  // mts0 表示第一个方法的参数列表 class 数组
  public static Class[] mts0;
  // mts1 表示第二个方法的参数列表 class 数组
  public static Class[] mts1;
  // mts2 表示第三个方法的参数列表 class 数组
  public static Class[] mts2;
  // mts3 表示第四个方法的参数列表 class 数组
  public static Class[] mts3;
  
  public String[] getPropertyNames() {return pns;}
  
  public boolean hasProperty(String paramString) {return pts.containsKey(paramString); }
  
  public Class getPropertyType(String paramString) {return (Class)pts.get(paramString); }
  
  public String[] getMethodNames() {return mns;}
  
  public String[] getDeclaredMethodNames() {return dmns;}
  
  public void setPropertyValue(Object paramObject1, String paramString, Object paramObject2) {
    DemoServiceImpl demoServiceImpl;
    try {demoServiceImpl = (DemoServiceImpl)paramObject1;
    } catch (Throwable throwable) {throw new IllegalArgumentException(throwable);
    } 
    if (paramString.equals("setterMethod")) {demoServiceImpl.setSetterMethod((Object)paramObject2);
      return;
    } 
    throw new NoSuchPropertyException("Not found property \"" + paramString + "\" filed or setter method in class com.alibaba.dubbo.demo.DemoServiceImpl.");
  }
  
  public Object getPropertyValue(Object paramObject, String paramString) {
    DemoServiceImpl demoServiceImpl;
    try {demoServiceImpl = (DemoServiceImpl)paramObject;
    } catch (Throwable throwable) {throw new IllegalArgumentException(throwable);
    } 
    if (paramString.equals("getterMethod"))
      return demoServiceImpl.getGetterMethod(); 
    if (paramString.equals("isMethod")) {new Boolean();
      super(new Boolean());
      return new Boolean();} 
    throw new NoSuchPropertyException("Not found property \"" + paramString + "\" filed or setter method in class com.alibaba.dubbo.demo.DemoServiceImpl.");
  }
  
  public Object invokeMethod(Object paramObject, String paramString, Class[] paramArrayOfClass, Object[] paramArrayOfObject) throws InvocationTargetException {
    DemoServiceImpl demoServiceImpl;
    try {demoServiceImpl = (DemoServiceImpl)paramObject;
    } catch (Throwable throwable) {throw new IllegalArgumentException(throwable);
    } 
    try {if (!"getGetterMethod".equals(paramString) || paramArrayOfClass.length != 0) {if (!"isIsMethod".equals(paramString) || paramArrayOfClass.length != 0) {if (!"sayHello".equals(paramString) || paramArrayOfClass.length != 1) {if (!"setSetterMethod".equals(paramString) || paramArrayOfClass.length != 1)
              throw new NoSuchMethodException("Not found method \"" + paramString + "\" in class com.alibaba.dubbo.demo.DemoServiceImpl."); 
            demoServiceImpl.setSetterMethod((Object)paramArrayOfObject[0]);
            return null;
          } 
          return demoServiceImpl.sayHello((String)paramArrayOfObject[0]);
        } 
        new Boolean();
        super(new Boolean());
        return new Boolean();} 
      return demoServiceImpl.getGetterMethod();} catch (Throwable throwable) {throw new InvocationTargetException(throwable);
    } 
  }
}

5.7.2 DelegateProviderMetaDataInvoker

  DelegateProviderMetaDataInvoker(Bo),仅实现 Invoker 接口,用于封装 AbstractProxyInvoker 匿名内部类实例 和 ServiceConfig 实例,向后续逻辑传递重要实例。(可以理解为用于远程发布(Netty)的第二阶段 Invoker)

代码示例 Invoker 实现类 DelegateProviderMetaDataInvoker 重要成员变量

public class DelegateProviderMetaDataInvoker<T> implements Invoker {
    // invoker 表示 AbstractProxyInvoker 实例
    protected final Invoker<T> invoker;
    // metadata 表示 ServiceConfig 实例,用于封装 <dubbo:service> 配置
    private ServiceConfig metadata;

5.7.3 RegistryProtocol.InvokerDelegete

  InvokerDelegete(Bo),是 RegistryProtocol(用于向注册中心注册提供者服务)的内部类,实现 Invoker 接口,封装 DelegateProviderMetaDataInvoker 实例和 URL 对象(URL 对象是用于传入 DubboProtocol,发布提供者的远程 Netty 服务参数)。(可以理解为用于远程发布(Netty)的最终阶段 Invoker)

代码示例 Invoker 实现类 RegistryProtocol.InvokerDelegete 重要成员变量

    public static class InvokerDelegete<T> extends InvokerWrapper<T> {
        
        // invoker 表示 DelegateProviderMetaDataInvoker 实例
        private final Invoker<T> invoker;
        
        // URL 对象是用于传入 DubboProtocol,发布提供者的远程 Netty 服务参数
        // 例:dubbo:// 本地 IP:20880/com.alibaba.dubbo.demo.DemoService?accesslog=true&anyhost=true&application=demo-provider&bean.name=com.alibaba.dubbo.demo.DemoService&bind.ip= 本地 IP&bind.port=20880&dubbo=2.0.2&generic=false&group=testGroup&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello,setSetterMethod,getGetterMethod,isIsMethod&pid=28756&revision=1.0&side=provider&timeout=3000&timestamp=1586484624885&version=1.0
        private final URL url;

5.8 Protocol

  接口 Protocol(Bo)有很多实现(比如 Injvm 协议 InjvmProtocol(用于发布提供者的本地服务)、Dubbo 协议 DubboProtocol(用于发布提供者的远程 Netty 服务)、Registry 协议 RegistryProtocol(用于向注册中心注册服务))。Protocol 的本质就是将 Invoker 作为提供服务对象发布出去。

代码示例 1 Protocol 接口

public abstract class AbstractProtocol implements Protocol {
    
    // InjvmProtocol 和 DubboProtocol 会在调用 export 方法发布服务时,存入值
    protected final Map<String, Exporter<?>> exporterMap = new ConcurrentHashMap<String, Exporter<?>>();

代码示例 2 InjvmProtocol – 重要成员变量

public class InjvmProtocol extends AbstractProtocol implements Protocol {

    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {...}
    

代码示例 3 DubboProtocol- 重要成员变量

public class DubboProtocol extends AbstractProtocol implements Protocol {
    
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {...}
    

代码示例 4 RegistryProtocol- 重要成员变量

public class RegistryProtocol implements Protocol {
    
    // protocol 默认是 DubboProtocol 实例(本 Demo 采用默认实例);用于执行远程服务(Netty)发布操作;protocol 属性是 Dubbo 自适应到 RegistryProtocol 实例后,自动装配的
    private Protocol protocol;

    // bounds 译:限制范围。// bounds 是为了避免同一个服务的同一个协议(例:dubbo)重复暴露;Dubbo 暴露服务时,将注册服务到注册中心的代码和发布远程 Netty 服务的代码写在了同一个 RegistryProtocol#export 方法里。故造成:如果配置了多个注册中心集群,Dubbo 会以每个协议,遍历所有的注册中心集群执行 RegistryProtocol#export 方法,但是发布 Netty 服务不关心注册中心,只需要一个协议发布一次就可以了,所以使用 bounds。以发布远程 Netty 服务的 url 为 key,暴露服务后的 Exporter 为 value,避免重复暴露
    // 例:{"dubbo:// 本地 IP:20880/com.alibaba.dubbo.demo.DemoService...()" -> ExporterChangeableWrapper 实例}
    private final Map<String, ExporterChangeableWrapper<?>> bounds = new ConcurrentHashMap<String, ExporterChangeableWrapper<?>>();

    // registryFactory 默认是 ZookeeperRegistryFactory 实例(本 Demo 采用默认实例);向注册中心注册服务的操作是 ZookeeperRegistry 对象负责的;而 ZookeeperRegistry 对象是 registryFactory 工厂生产的;详见章节“ZookeeperRegistryFactory”private RegistryFactory registryFactory;
    
    @Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {...}

5.9 Exporter

  接口 Exporter(Bo),有多种实现(比如:本地服务出口 InjvmExporter、远程服务出口 DubboExporter 等)。中文翻译”出口方“,顾名思义,就是当 Protocol 发布完 Invoker 服务之后,会返回一个新创建的 Exporter 实例;这个 Exporter 实例持有发布的 Invoker 实例,而且还有停止 Invoker 发布的功能;举个例子:消费者如果想要取得本地发布的服务 AbstractProxyInvoker,就是从这个 InjvmExporter 这获取(出口)。

代码示例 1 Exporter 接口

public interface Exporter<T> {Invoker<T> getInvoker();

    void unexport();}

代码示例 2 本地 Export- 重要成员变量

class InjvmExporter<T> extends AbstractExporter<T> {

    // exporterMap 存储着所有本地发布的 InjvmExporter;(InjvmProtocol 负责将 InjvmExporter 放入 exporterMap 里)// {"testGroup/com.alibaba.dubbo.demo.DemoService:1.0"-> InjvmExporter 实例};private final Map<String, Exporter<?>> exporterMap;

    // exporterMap 里的 key;例:"testGroup/com.alibaba.dubbo.demo.DemoService:1.0"
    private final String key;
        
    // 继承自 AbstractExporter
    private final Invoker<T> invoker;

代码示例 3 远程 Export- 重要成员变量

public class DubboExporter<T> extends AbstractExporter<T> {

    // 例:"testGroup/com.alibaba.dubbo.demo.DemoService:1.0:20880"
    private final String key;
    
    // {"testGroup/com.alibaba.dubbo.demo.DemoService:1.0:20880"-> DubboExporter 实例};// exporterMap 存储着所有远程发布的 DubboExporter;由 DubboProtocol 传入
    private final Map<String, Exporter<?>> exporterMap;
    
    // 继承自 AbstractExporter
    private final Invoker<T> invoker;

5.10 ServiceConfig

  服务暴露配置类(Pojo),被 ServiceBean 继承。在应用系统中,是以 ServiceBean 的形态注册到 Spring 容器当中去的,监听 Spring 的启动后,执行服务的暴露动作。

代码示例 重要成员变量

public class ServiceConfig<T> extends AbstractServiceConfig {
    
    /** 配置项 */

    // 接口实现类实例,对应 <dubbo:service ref="demoService" > 中的 ref
    private T ref;
    
    // interfaceName 对应 <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" > 中的 interface
    private String interfaceName;
    
    // 对应 <dubbo:service group="testGroup" > 中的 group
    protected String group;
    
    // 对应 <dubbo:service version="1.0" > 中的 version
    protected String version;
    
    // 对应 <dubbo:service timeout="3000" > 中的 timeout
    protected Integer timeout;
    
    /** 非配置项 */
   
    // 接口全局限定名(服务路径),默认是 interfaceName 的值
    private String path;
    
    // interfaceClass 表示发布的接口 Class 对象,通过 interfaceName 反射而来
    private String interfaceClass;
    
    // 继承自 AbstractInterfaceConfig
    // 对应 <dubbo:application>
    protected ApplicationConfig application;
    
    // 继承自 AbstractInterfaceConfig
    // protocols 表示 ProtocolConfig 实例(对应 <dubbo:protocol>)集合,本 Demo 中,只有一个元素
    protected List<ProtocolConfig> protocols;

    // 继承自 AbstractInterfaceConfig
    // registries 表示 RegistryConfig 实例(对应 <dubbo:application>)集合,本 Demo 中,只有一个元素
    protected List<RegistryConfig> registries;
    
    // protocol 负责服务的发布,自适应扩展实例(来自第二种实现方式)private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
    
    // proxyFactory 负责 Invoker 的生成,自适应扩展实例(来自第二种实现方式)private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();}

5.11 Transporter

  接口 Transporter(Bo)(网络传输者),有很多网络传输实现(Grizzly 实现 GrizzlyTransporter、Mina 实现 MinaTransporter、Netty 实现 netty.NettyTransporter、Netty4 实现 netty4.NettyTransporter)用于将发布的服务通过网络暴露出去。Dubbo 底层默认使用 Netty4 实现。

5.12 Exchanger

  Exchanger(信息交换层)(Bo),试想一下,消费者通过网络传输传递请求数据给提供者,提供者通过网络传输返回响应数据,传输的数据肯定是序列化和反序列化后的结果,所以要参与到 Dubbo 的逻辑,就一定需要 Exchanger 转换数据类型;不仅如此,为了要满足 Dubbo RPC 更多需求,还需要 Exchanger 承担起更多职责,比如:同步请求转为异步请求等。

5.13 ZookeeperRegistry

  1. 介绍

  ZookeeperRegistry(BO),封装了向 Zookeeper 注册服务的操作。默认使用 CuratorZookeeperTransporter 对象负责注册服务;上文已经说过,向 Zookeeer 注册服务,就是在 Zookeeper 上创建一个对应 < dubbo:service > 的节点目录。

  2. 实现逻辑

  ZookeeperRegistry 创建一个 < dubbo:service > 节点目录的过程:

  1. 首先会创建持久目录:/dubbo;
  2. 然后创建持久目录 /dubbo/com.alibaba.dubbo.demo.DemoService
  3. 然后创建持久目录 /dubbo/com.alibaba.dubbo.demo.DemoService/providers
  4. 最终创建临时节点:/dubbo/com.alibaba.dubbo.demo.DemoService/providers/dubbo%3A%2F%2F192.168.0.110%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26dubbo%3D2.0.2%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%2CsetSetterMethod%2CgetGetterMethod%2CisIsMethod%26pid%3D16496%26side%3Dprovider%26timestamp%3D1575900851450

代码示例 2-1 重要成员变量

public class ZookeeperRegistry extends FailbackRegistry {
    
    // zkClient 默认是 CuratorZookeeperClient 实例(本 Demo 采用默认实例); 负责创建 Zookeeper 目录
    private final ZookeeperClient zkClient;

5.14 ZookeeperRegistryFactory

  ZookeeperRegistryFactory(Bo),继承自 AbstractRegistryFactory,用于生产 ZookeeperRegistry 实例。

代码示例 重要成员变量

public abstract class AbstractRegistryFactory implements RegistryFactory {// REGISTRIES 表示 ZookeeperRegistry 实例缓存,避免重复创建。例:{"zookeeper://10.20.153.10:2181/com.alibaba.dubbo.registry.RegistryService"-> ZookeeperRegistryFactory 实例}
    private static final Map<String, Registry> REGISTRIES = new ConcurrentHashMap<String, Registry>();

<br/>

加入我的粉丝社群,阅读全部内容

  从学习到面试,从面试到工作,从 coder 到 TeamLeader,每天给你答疑解惑,还能有第二份收入,这样的知识星球,难道你还要犹豫!

退出移动版