码迷,mamicode.com
首页 > 其他好文 > 详细

探究Dubbo的拓展机制: 下

时间:2020-01-13 21:47:39      阅读:79      评论:0      收藏:0      [点我收藏+]

标签:原来   取出   拓展   容器   rect   static   处理   activate   北京   

承接上篇, 本篇博文的主题就是认认真真捋一捋, 看一下 Dubbo是如何实现他的IOC / AOP / 以及Dubbo SPI这个拓展点的

总览:

本篇的话总体上分成两部分进行展开

  • 第一点就是 Dubbo在启动过程中加载原生的配置文件中提供的被@SPI标记的实现类:

技术图片

  • 第二就是Dubbo加载程序员后续添加进去的被@SPI标注的接口和实现类, 进而探究 Dubbo的IOC / AOP / 以及Dubbo SPI这个拓展点机制

技术图片

环境的初始化

入口程序

如下代码是追踪的起点:

我也是看了好多遍才勉强将这个过程整理明白一些, 但是根据以往的经验来说, 过一俩月之后我可能就会淡忘这个流程... 为了让自己一段时间后快速的回忆起来这个流程, 所以我要对自己说下面一段话

Dubbo的拓展点编码实现中, 会反反复复的出现下面这段代码

ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(XXX.class); extensionLoader.getExtension("XXX");

先说这段代码在干什么? 其实上它就是在为 Dubbo原生的SPI接口, 或者是用户提供的SPI接口 结合SPI的配置文件中的配置, 找到这些SPI接口的实现类, 并且这个过程中穿插Dubbo的IOC已经AOP机制

不得不服气, 这一段代码的实现, 因为这段代码设计不仅仅能加载Dubbo提供的原生的SPI接口, 也能加载使用 用户自定义的SPI , 详细的过程在下文中展开, 妙!!!

技术图片

明星类 ExtensionLoader.java

应该得, 隆重的介绍一下这个明星类 ExtensionLoader.java

从名字上看, ExtensionLoader , 见名知意, 拓展点的加载器, 那什么是Dubbo的拓展点呢? 拓展点就是Dubbo允许用户参与到Dubbo环境的初始化这个过程中来, 允许用户定制Dubbo行为, 诸如 Dubbo的 SPI / IOC / AOP (上一篇博文主要的学习内容就是Dubbo的拓展点的使用)

见名知意: ExtensionLoader 拓展点的加载器, 就是使用这个封装类, 我们可以加载Dubbo提供的拓展点, 说白了, 其实加载为SPI接口找到实现类, 以及完成这些实现类之间的 AOP增强 + IOC 依赖注入的过程

此外这个类很有必要看, 为啥呢? 第一点就是说它的设计很巧妙, 代码的抽象和复用能力都很好, 第二点就是说, 我们可以一睹大神们的风采, 如果 实现自己的SPI , 如何实现自己的IOC AOP

技术图片

入口方法

下面就是入口程序中的第一个方法, getExtensionLoader(Class<T> type) 很简单, 就是根据类型找到对应的 ExtensionLoader, 待会Dubbo就会为我添加进去SPI接口生成这样的 ExtensionLoader : org.apache.dubbo.common.extension.ExtensionLoader[com.changwu.ioc.api.PersonInterface]

当然Dubbo也有自己原生的ExtensionLoader

从我的入口程序来看, 很显然, 我传递进来的 type = PersonInterface , 方法执行的逻辑如下

  • 对type参数进行校验
  • 检查缓存中是否存在 PersonInterface的 ExtensionLoader
    • 如果有的话, 返回这个现存的ExtensionLoader
    • 如果不存在就创建一个新的
 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Extension type == null");
        }
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
        }
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }

        //todo 这里体现了缓存机制, EXTENSION_LOADERS 其实就是 CurrentHashMap
        //todo EXTENSION_LOADERS  是 CurrentHashMap , 每一种interfaceType 都对应一个 ExtensionLoader , 但是这些 ExtensionLoader全部被维护在 这个EXTENSION_LOADER中
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

ExtensionLoader蹊跷的构造方法

那我们是第一次进来, 肯定是没有的, 因此我们看他是如何进行new ExtensionLoader<T>(type)的, 所以跟进看一下它的构造方法

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        // 对于一个接口,比如PersonInterface接口,有两种实现类,一种就是我们自定义的实现类,比如Student,还有一种就是代理类,对于代理类,可以由我们自己实现,也可以让Dubbo帮我们实现,而代理类主要就是依赖注入时使用
        // todo ExtensionFactory 是dubbo的拓展机制工厂, 它里面封装了 Dubbo的SPI拓展机制和Spring的拓展机制
        // todo ExtensionLoader.getExtensionLoader(ExtensionFactory.class) ===>  获取  自适应的extension
        //  ||          ||          ||             ||               ||          ||
        // todo ExtensionLoader.getExtensionLoader(PersonInterface.class)  ===>  昌武, 你获取的是:  extension("human")
        // todo 你看这是不是挺清晰的
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

在上面的构造方法中, 就有蹊跷了, 逻辑如下

  • 将type = PersonInterface 保存起来
  • 获取 ExtensionFactory.class 类型的 ExtensionLoader
  • 获取 ExtensionFactory.class 类型的 ExtensionLoader的自适应的 Extension

我所说的有蹊跷的地方: 我们本来不是前来创建PersionInterface 的ExtensionLoader吗? 怎么先创建 ExtensionFactory的 ExtensionLoader呢?

(因为在创建ExtensionFactory的 ExtensionLoader的过程中会去加载Dubbo提供的其他的诸如SpiExtensionFactory这一类的实现, 这些默认的实现的作用就是辅助Dubbo再去解析用户提供的SPI实现体系)

下面看看这个 ExtensionFactory.class类

没错! 它被添加上了@SPI的注解, 说明和 我们的PersonInterface一样, 是DubboSPI

技术图片

那好吧, 既然Dubbo想先完成它的实例化, 就往下看, 我在博文开头就不停的说, Dubbo设计的很好, 这里不就递归调用getExtensionLoader(type= ExtensionFactory.class)了吗? 不出意外的话, 再一次的 进去构造方法, 然后在这个三元判断表达式中发现了 type == ExtensionFactory.class ? null : ExtensionLoader.getE... 前半部分是满足条件的, 然后设置objectFactory=null, 完成 ExtensionFactory的构造, 然后执行getAdaptiveExtension()

获取自适应的Extension

这个 getAdaptiveExtension() 同样需要好好的看看, 见名知意, 返回一个自适应的 Extension, 说白了就是返回Dubbo通过字符拼接出来的Extension类

下面看看这个 getAdaptiveExtension() 源码如下:

    @SuppressWarnings("unchecked")
    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                // todo 为了线程安全 , 使用了双重同步锁
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            // todo 跟进去
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

着重跟进 instance = createAdaptiveExtension();方法, 源码如下: 主要逻辑如下:

  • 获取出 AdaptiveExtensionClass
    • 启动过程中, 第一次获取到的是 Dubbo提供的默认实现类, 叫 AdaptiveExtensionFactory
    • 第二次获取到的是 Dubbo为用户提供的SPI接口动态生成的实现类
  • 实例化AdaptiveExtensionClass
  • 对实例化的AdaptiveExtensionClass 进行依赖注入的操作

技术图片

加载SPI配置文件, 获取所有的ExtensionClass

ExtensionClass 可以直白的理解成 SPI 接口的实现类, 或者是wrapper类

上面的代码中想要获取一个 AdaptiveExtensionClass() 那么问题来了, 从哪里获取呢? 跟进getAdaptiveExtensionClass()

没错就在下面的

    private Class<?> getAdaptiveExtensionClass() {
        // todo 加载配置文件
        getExtensionClasses();
        // todo 在前一步加载配置文件时, 加载到了 AdaptiveExtensionFactory, 这里返回的就是 CachedAdaptiveClass
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        // 如果没有手动实现接口的代理类,那么Dubbo就会自动给你实现一个
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

往下跟进getExtensionClasses();

下面的函数中维护着一个 cachedClasses 它是一个Map , key=String value= Class ; 说白了, 存放的就是从SPI配置文件中读取配置信息

  // 实际上就是将配置中的 key=value 读取装在进map中
    private Map<String, Class<?>> getExtensionClasses() {
        // todo  cachedClasses是 ExtensionLoader的属性: Holder<Map<String, Class<?>>> cachedClasses
        // todo  用于存储提前约定好了存储在 类路径下的  METE-INF/services  以及dubbo原生提供的扩展点
        // todo  同样是双重同步锁 + volatile  保证线程安全
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // todo 着重看这个函数
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

进行跟进loadExtensionClasses();

可以看到, Dubbo会按照约定读取下面几个配置文件中的配置信息, 下面我注释上的文件的全路径所对应的文件中会记录Dubbo原生的SPI的实现, 我们也能遵循这个规则提供自己的实现类

比如随便查看一个配置文件

技术图片

    // synchronized in getExtensionClasses
    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        // todo 跟进这个 loadDirectory() 方法, 看看
        // todo    META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());

        // todo    META-INF/dubbo/internal/com.alibaba.dubbo.common.extension.ExtensionFactory
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));

        // todo    META-INF/dubbo/org.apache.dubbo.common.extension.ExtensionFactory
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());

        //todo     META-INF/dubbo/com.alibaba.dubbo.common.extension.ExtensionFactory
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));

        //todo     META-INF/services/org.apache.dubbo.common.extension.ExtensionFactory
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());

        //todo     META-INF/services/com.alibaba.dubbo.common.extension.ExtensionFactory
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }

下面看一下处理的详细细节信息:

  • 如果从配置文件中读取到的 SPI的实现类添加了@Adaptive注解, 就先缓存起来
    • Dubbo将 AdaptiveExtensionFactory.java暂时缓存起来了
    • 技术图片
  • 没添加@Adaptive的话, 同样将其缓存在不同额容器中, 稍后使用
    • Dubbo创建的实例对象是: SpiExtensionFactory,java
    • 技术图片
    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        // todo 查看有没有标注 @Adaptive 注解, 如果标注有这个注解的话, 那么就将他暂时存放起来, 而不是执行下面的逻辑, 构造出对象来
        // todo 昌武, 你看, 你在验证ioc时, 你提供的PersonInterface很显然是存在这个@Adaptive注解 ,他会在上面提到getAdaptiveClasss() 后 然后newInstance()创建实例

        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }

            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, name);
                }
            }
        }
    }

小结: 到这里基本上就到了Dubbo的底层确实会去读取配置文件, 根据他们的配置情况, 缓存在不同容器中

好, 到这里上面所说的getExtensionClasses(); 方法就说完了, 回到下面的方法中

技术图片

得到了AdaptiveExtensionFactory类之后, 接着就通过反射创建的它实例对象, 所以说, 我们要去看他的构造方法, 如下:

  • AdaptiveExtensionFactory继承了 ExtensionFactory, 因此它需要重写 getExtension(Class<T> type, String name)
  • 重点看他的构造方法

又看到了这行代码ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class); 这行代码的执行流程其实已经说过了, 这次根据名称获取的 Extension是 SpiExtensionFactory, 并将它维护起来

技术图片

新的问题就来了, 这个SPIExtensionFactory是谁呢? 有啥用呢 看下面, 说白了, 用它处理添加有Dubbo的SPI注解的接口, 然后尝试获取这些接口的 实现

技术图片

构建方法执行完成了, 也就说明 AdaptiveExtension 创建完成了, 刚才所说的 createAdaptiveExtension

injectExtension其实就是回去做IOC / AOP 相关的操作, 现在我们跟踪的实现类是 AdaptiveExtension 它没有依赖其他的属性, 但是我提供的PersonInterface依赖了, 所以说我们暂时先不进如这个方法,稍后再进去查看他的实现

技术图片

小结: 下图是我们的启动类, 到目前为止, 我们就看完了启动类的第一行代码做了什么, 那它主要是做了哪些事情呢?

  • 实例化 : AdaptiveExtensionFactory
  • 实例化 : SPIExtensionFactory
    • 可用来处理用户后续添加进来的SPI相关逻辑
  • 实例化 : 用户提供的Spi接口的 ExtensionLoader

技术图片

Dubbo的IOC细节

下面就继续看这行extensionLoader.getExtension("human") , 看他的返回值, 很明显, 就是要返回我们需要的personInterface的实现类, 并在这个过程中穿插这IOC和AOP的逻辑

回顾一下实验的环境, 重新整理一下思路: 我们想获取出 key = human的 PersonInterface的实现类, 这实现类长下面这样:

public class Human implements PersonInterface {

    private PersonInterface personInterface;

    //todo 第一个关注点: 我们的关注点就是说, Human 会帮我们将哪一个实现类当成入参注入进来呢?
    //todo 答案是 URL ,dubbo自己封装的URL,  统一资源定位符, dubbo 会解析入参位置的 url中封装的map
    //todo map中的key 与 PersonInteface中的使用   @Adaptive("person") 注解标记的value对应, 那么值就是将要注入的实际类型
    //todo 第二个关注点: dubbo底层很可能是通过反射使用构造方法完成的属性注入
    public void setPersonInterface(PersonInterface personInterface) {
        this.personInterface = personInterface;
    }

    @Override
    public String getName(URL url) {
        System.out.println("i am Human ");
        return "i am Human + " + personInterface.getName(url);
    }
}

可以很直接的看到, 这个实现类其实是依赖了一个 PersionInterface的属性,需要将这个属性注入给他, 于是问题来了, 注入的是谁呢? 下面继续往下拉看

进入下面的方法, 主要逻辑如下

  • 根据那么取出ExtensionClasses的 Class 对象,
    • 这获取出来的对象就是我们前面所说的,就是读取SPI配置文件时获取出来的对象
  • 调用injectExtension()方法, 完成对象依赖属性的注入
  • 实现Dubbo的AOP , 完成对象方法切面的增强

技术图片

我们先看下: injectExtension(instance)的实现细节:

主要逻辑如下:

  • 通过反射获取出对象的所有的方法
    • 如果不是setter方法就返回 (体现出, Dubbo的依赖注入是借助setter方法实现的)
    • 如果添加的@Disable注解, 表示明确指定不会进行注入
    • 尝试获取出当前对象所依赖的对象, 也就是下面的objectFactory.getExtension(pt,property)
      • 其中objectFactory就是前面创建出来的SPIExtensionFactory
      • pt=PersonInterface的Class 描述对象
      • property 是从 setPersonInterface()方法中截取出来的: personInterface 字符串

技术图片

上图中的主要目的就是完成依赖注入, 什么依赖注入呢? 就是在 Human.java中 依赖了一个PersonInterface类型的属性, Dubbo需要帮我填充上 , HumanInterface.java 中锁依赖的那个具体的实现类是谁呢? 就是在上面函数中的通过 objectFactory.getExtension(Class,name) 动态生成出来的

当我们继续跟进这个getExtension(), 就会发现下面的现象, 看我在下图中标出来的绿色部分, 可以发现 , 他获取出来的 ExtensionLoader全称如下: 它就是Dubbo我们生成出来的代理 ExtensionLoader

技术图片

再进一步, 通过loader 获取出自适应的拓展类: getAdapativeExtension()通过反编译看一下生成的Interface是谁, 可以看一下,它的实现, 这就是为什么Dubbo通过URL就能知道该注入谁, 用谁取干活

技术图片

Dubbo的AOP细节 (wrapper)

先说啥是AOP, 就是面向切面编程, 其实说白了就是对现有的对象进行增强

Dubbo是怎么做的呢? 按照Dubbo的约定, 我们的这样编码:即 通过继承+构造方法 实现AOP , 就像下面这样

技术图片

Dubbo的底层实现: 处理AOP的逻辑在下面

技术图片

Dubbo会从SPI配置文件中找到我们添加就进去的Wrapperlei, 通过构造方法反射出他们的实例,, 重要的是将反射出来的这个实例替换成了原来未被增强的 对象, 就跟java的感觉就像是升级版的静态代理一样

最后打一个小广告: 我是bloger 赐我白日梦, 本科大三在读, 热衷java研发, 期望有一份Java相关实习岗位的工作, 可以全职实习半年左右, 最理想城市是北京, 求大佬的内推哇

探究Dubbo的拓展机制: 下

标签:原来   取出   拓展   容器   rect   static   处理   activate   北京   

原文地址:https://www.cnblogs.com/ZhuChangwu/p/12189152.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!