zoukankan      html  css  js  c++  java
  • 探究Dubbo的拓展机制: 下

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

    总览:

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

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

    概要1

    • 第二就是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

    extensionLoader

    入口方法

    下面就是入口程序中的第一个方法, 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

    ExtensionFactory

    那好吧, 既然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 进行依赖注入的操作

    crateAdaptiveExtension

    加载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暂时缓存起来了
      • 图5
    • 没添加@Adaptive的话, 同样将其缓存在不同额容器中, 稍后使用

      • Dubbo创建的实例对象是: SpiExtensionFactory,java
      • SPIExtensionFactory
        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(); 方法就说完了, 回到下面的方法中

    crateAdaptiveExtension

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

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

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

    图1

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

    SPIExtensionFactory

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

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

    crateAdaptiveExtension

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

    • 实例化 : 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 , 完成对象方法切面的增强

    ioc和aop的预览

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

    主要逻辑如下:

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

    ioc

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

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

    图3

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

    反编译

    Dubbo的AOP细节 (wrapper)

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

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

    wapper2

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

    wrapper

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

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

  • 相关阅读:
    由保存当前用户引发的springboot的测试方式postman/restlet还是swagger2
    VS集成opencv编译C++项目遇到的问题
    利用StringUtils可以避免空指针问题
    springboot集成Guava缓存
    Oracle 课程四之索引
    Oracle 课程三之表设计
    Oracle 课程二之Oracle数据库逻辑结构
    Oracle 课程一之Oracle体系结构
    Oracle权限一览表
    Informatica元数据库解析
  • 原文地址:https://www.cnblogs.com/ZhuChangwu/p/12189152.html
Copyright © 2011-2022 走看看