dubbo内核简介(附部分源码解读)

前言

dubbo是微服务架构常用的框架,但是光会用是不行的,还要深入了解它的原理,学习它的思想和书写规范。本文是初步了解dubbo内核,附部分源码解读。后续会深入了解dubbo源码。

一、总结

    1.dubbo简介

  • dubbo内核:由“微服务+插件”组成。
  • dubbo内核的构成:SPI、Adaptive、Wrapper和Activate。

SPI是微内核,其他三个是插件。

dubbo的设计原则(实现原理):AOP、IOC与动态编译Compiler。

所以, AOP、 IoC,与动态编译 Compiler 又称为 Dubbo 内核原理。

    2.JDK的SPI机制与DubboSPI机制的不同

       2.1 SPI

Service Provider Interface,服务提供者接口,是一种服务发现机制。

JDK 的 SPI 规范规定:

  • 接口名:可随意定义
  • 实现类名:可随意定义
  • 提供者配置文件路径:其查找的目录为 META-INF/services
  • 提供者配置文件名称:接口的全限定性类名,没有扩展名。
  • 提供者配置文件内容:该接口的所有实现类的全限类性类名写入到该文件中,一个类名占一行

2.2 Dubbo 的 SPI 规范

  • 接口名:可以随意定义
  • 实现类名:在接口名前添加一个用于表示自身功能的“标识前辍”字符串
  • 提供者配置文件路径:在依次查找的目录为

META-INF/dubbo/internal

META-INF/dubbo

META-INF/services

  • 提供者配置文件名称:接口的全限定性类名,无需扩展名
  • 提供者配置文件内容:文件的内容为 key=value 形式,value 为该接口的实现类的全限类性类名,key 可以随意,但一般为该实现类的“标识前辍”(首字母小写)。一个类名占一行。
  • 提供者加载:ExtensionLoader 类相当于 JDK SPI 中的 ServiceLoader 类,用于加载提供者配置文件中所有的实现类,并创建相应的实例。

        两者不同文档有详细介绍;主要区别是:

  1. dubbo可以配置文件是key=value,value是实现类的全限定名称,key是作为该实现类的“”标识前缀“。
  2. JDK的SPI机制会加载已配置接口的所有实现类;而Dubbo的SPI机制可以选择指定实现类加载。

3.dubbo的注解

        3.1.@SPI

没有其他功能,就是一个标识注解,表明它是一个SPI接口。

     注:没有@SPI注解的接口,在getExtension(Class<T> type) 时,会抛出异常。源码如下:

 

 

 3.2 Adaptive 机制

        Adaptive 机制,即扩展类的自适应机制。即 ExtentionLoader 在加载扩展类时,若没有指定要加载的扩展名,则会直接加载默认的扩展类。即其会自动匹配,做到自适应。其是通过@Adaptive 注解实现的。

        dubbo框架中有自适应类的接口就两个:

  1.     ExtensionFactory:它的自适应类——AdaptiveExtensionFactory
  2.     Compiler:它的自适应类——AdaptiveCompiler

@Adaptive 注解除了这两个是修饰类的,其他的都是修饰方法的。

    3.2.1 Adaptive用法

  1. @SPI("wechat")
  2. 标注在类上
  3. 标注在方法上

    3.3Wrapper 机制(暂时只做了解)

        Wrapper 机制,即扩展类的包装机制。就是对扩展类中的 SPI 接口方法进行增强,进行包装,是 AOP 思想的体现,是 Wrapper 设计模式(装饰者设计模式)的应用。一个 SPI 可以包含多个 Wrapper。

总结:

4.Dubbo SPI 源码初步解析

        4.1 生成Protocol

            启动dubbo提供者容器,会走到ServiceConfig类生成静态变量Protocol方法。
 
            4.1.1 首先看这个 方法的前半部分 ExtensionLoader.getExtensionLoader(Protocol.class):
 
 

继而调用ExtensionLoader(Protocol.class)方法:

 
内部会调用new ExtensionLoder(type)构造方法:

这里就很重要了,红框的部分,它又调用了我们第一步调用的ExtensionLoader.getExtensionLoader(ExtesionFactory.class)。
也就是说,生成Protocol的整个过程,其实调用了两次SPI。

 总思路图:
 
 

4.1.2 再来看这个方法的后半部分

 
1.继而走到了getAdaptiveExtension():
 
看到这个代码,首先我们应该想到什么呢?——双重检查锁,单例模式都会用到
但是双重检查锁有线程安全问题,所以我们会想到什么?——相关属性一定是用volatile修饰的
2. 接下来调用createAdaptiveExtension()
 
 
  • getAdaptiveExtensionClass()获取到adaptive类的class
  • injectExtension()用于完成IOC注入

3.接下来调用getAdaptiveExtensionClass()


this.getExtensionClasses()作用:将普通扩展类,adaptive类,wrapper类,active类进行缓存。

4.再进入getExtensionClasses()方法进行查看

cachedClasses缓存的是普通扩展类与activate类,不包含adaptive类与wrapper类

5.继续追到loadExtensionClasses()方法中

this.cacheDefaultExtensionName():缓存SPI接口的默认扩展名。
loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type):加载指定接口的资源。

6.接下来进入loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type)中:

fileName是什么呢?
——就是类路径下的接口名,如下图

思考:为什么命名是复数urls?
因为不止这一处有此文件,其他模块下也会有一模一样的文件名。
例如:org.apache.dubbo.common.extension.ExtensionFactory在dubbo-comon和dubbo-config-spring中都有。

7.继续跟loadResource()方法:
因屏幕截不下,把代码复制上来:

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
    try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8));
        Throwable var5 = null;
        try {
            String line;
            try {
                while((line = reader.readLine()) != null) {
                    int ci = line.indexOf(35);
                    if (ci >= 0) {
                        line = line.substring(0, ci);
                    }

                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf(61);
                            if (i > 0) {
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                this.loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } catch (Throwable var19) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + this.type + ", class line: " + line + ") in " + resourceURL + ", cause: " + var19.getMessage(), var19);
                            this.exceptions.put(line, e);
                        }
                    }
                }
            } catch (Throwable var20) {
                var5 = var20;
                throw var20;
            }
        } finally {
            if (reader != null) {
                if (var5 != null) {
                    try {
                        reader.close();
                    } catch (Throwable var18) {
                        var5.addSuppressed(var18);
                    }
                } else {
                    reader.close();
                }
            }
        }
    } catch (Throwable var22) {
        logger.error("Exception occurred when loading extension class (interface: " + this.type + ", class file: " + resourceURL + ") in " + resourceURL, var22);
    }
}

高亮的两处代码有一些疑问:
 在其他版本中,int类型35和61会被字符串‘#’和‘=’代替,难道可以等价替换?

 

  8.继续跟loadClass方法

可以直观的看到缓存了cacheAdaptiveClass,cacheWrapperClass。并调用cacheActivateClass()缓存了activateClass。isWrapperClass(clazz)方法:判断该类是否是wrapper类

从代码可以分析,只要含有有参构造方法,且参数是SPI接口,就认为该类是wrapper类。
wrapper类代码示例:

 9 在回到loadClass
 

10.cacheActivateClass()
 缓存activete类,兼容2.6

11.saveInExtensionClass()
可以看出,其是将当前 name 与扩展类配对后放到了 extensionClasses 中了。

 4.1.3 获取 Protocol 的 extensionLoader
前面的代码执行完毕,然后一层层的返回,最终就返回到了这里。

此时 Protocol 的 ExtensionLoader 的 objectFactory 实例就创建完毕。返回其调用语句。 


好了,这次分析就到这里为止了,以后再深入解析dubbo源码。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值