Dubbo源码篇08---依赖注入和AOP在Dubbo中的实现

Dubbo源码篇08---依赖注入和AOP在Dubbo中的实现

  • 引言
  • 依赖注入
    • 使用实践
  • Wrapper机制
    • 使用实践
    • 注意


引言

前面三篇文章,我们从使用到原理,详细分析了一遍Dubbo SPI机制的实现原理:

  • Dubbo源码篇05—SPI神秘的面纱—使用篇
  • Dubbo源码篇06—SPI神秘的面纱—原理篇—上
  • Dubbo源码篇07—SPI神秘的面纱—原理篇—下

有了前面的铺垫,本文理解起来将会十分的轻松,对于依赖注入,我们首先想到的就是Spring中的@Autowired和@Resource注解,而AOP功能,则会首先联想到@Aspect注解。

对于Dubbo而言,其采用的是微核心+插件式架构,通常微核心都会采用 Factory、IoC、OSGi 等方式管理插件生命周期。考虑 Dubbo 的适用面,不想强依赖 Spring 等 IoC 容器,自已造一个小的 IoC 容器,也觉得有点过度设计,所以采用最简单的 Factory 方式管理插件。

所以对于Dubbo而言,其依赖注入和AOP也都是在其内部IOC基础上实现的,实现相比于Spring而言简单许多,所以废话不多说,我们直接开始Dubbo 依赖注入和AOP实现原理研究。

本文以普通扩展类的加载为总线,从使用层面验证之前原理篇中分析过的,关于依赖注入和Wrapper机制的代码。


依赖注入

我们先来简单回顾一下依赖注入部分的源代码:

createExtension方法是创建普通扩展类的核心方法:
在这里插入图片描述
injectExtension依赖注入的核心代码如下所示:

  private T injectExtension(T instance) {
    // 这里的扩展注入器不为空,在ExtensionLoader创建时会获取ExtensionInjector的自适应扩展类
    // 这里的injector即是ExtensionInjector扩展接口的的自适应扩展类AdaptiveExtensionInjector
    // 如果为空则直接返回当前实例对象,不进行依赖注入
        if (injector == null) {
            return instance;
        }
        try {
             // 遍历所有方法 --- 只包括本类和父类的public方法
            for (Method method : instance.getClass().getMethods()) {
                // 如果当前方法不是一个setXXXX()方法则继续处理下一个方法
                // public + set开头 + 只有一个参数
                if (!isSetter(method)) {
                    continue;
                }
                //校验当前方法上携带了@DisableInject注解吗,即禁止注入的当前属性,符合则跳过
                if (method.isAnnotationPresent(DisableInject.class)) {
                    continue;
                }
                // 第一个参数是原生类型(String、Boolean、Integer ...) 跳过
                Class<?> pt = method.getParameterTypes()[0];
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }
                try {
                    // 获取set方法对应的成员变量如setProtocol 属性为protocol
                    String property = getSetterProperty(method);
                    // 根据参数类型如Protocol和属性名字如protocol获取应该注入的对象
                    Object object = injector.getInstance(pt, property);
                    if (object != null) {
                        method.invoke(instance, object);
                    }
                }...
            }
        }...
        return instance;
    }

扩展依赖注入默认情况下为AdaptiveExtensionInjector:
在这里插入图片描述
AdaptiveExtensionInjector作为默认的扩展依赖注入自适应扩展点,当其被初始化时,会通过getExtensionLoader方法拿到ExtensionInjector扩展类型的所有扩展实现:
在这里插入图片描述

    @Override
    public void initialize() throws IllegalStateException {
        ExtensionLoader<ExtensionInjector> loader = extensionAccessor.getExtensionLoader(ExtensionInjector.class);
        List<ExtensionInjector> list = new ArrayList<ExtensionInjector>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        injectors = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getInstance(Class<T> type, String name) {
      // 遍历所有的扩展注入器并调用getinstance()方法,并取第一个返回
        for (ExtensionInjector injector : injectors) {
            T extension = injector.getInstance(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

ExtensionInjector是 Dubbo 源码中为数不多的自适应扩展实现实例,ExtensionInjector扩展接口的自适应扩展实现就是AdaptiveExtensionInjector,接口实现类默认有三个

  • SpiExtensionInjector: 根据实例 class 从 ExtensionLoader 中获取实例
  • ScopeBeanExtensionInjector: 从 Dubbo 自定义的beanfactory中获取实例
  • SpringExtenisonInjector: 从 Spring 的beanfactory中获取实例

这个AdaptiveExtensionInjector在初始化的时候会获取所有的ExtensionInjector的扩展,非自适应的,它本身是自适应的扩展。
在这里插入图片描述


使用实践

测试环境:

@SPI("spring")
public interface FrameWork {
    String getName(URL url);
    String getInfo();
}

public class Spring implements FrameWork {
    private FrameWork springBoot;

    public void setSpringBoot(FrameWork springBoot) {
        this.springBoot = springBoot;
    }

    @Override
    public String getName(URL url) {
        return "spring";
    }

    @Override
    public String getInfo() {
        return springBoot.getInfo()+" 流行的Spring框架";
    }
}


public class SpringBoot implements FrameWork{
    @Override
    public String getName(URL url) {
        return "springBoot";
    }

    @Override
    public String getInfo() {
        return "自动化的SpringBoot框架";
    }
}

SPI文件:

spring=com.adaptive.Spring
springBoot=com.adaptive.SpringBoot
guice=com.adaptive.Guice

测试类:

        ApplicationModel applicationModel = ApplicationModel.defaultModel();
        ExtensionLoader<FrameWork> extensionLoader = applicationModel.getExtensionLoader(FrameWork.class);
        FrameWork frameWork = extensionLoader.getExtension("spring");
        System.out.println(frameWork.getInfo());

直接运行上面的测试用例,会抛出异常,因为我们期望的是借助SpiExtensionInjector获取别名为springBoot的扩展实例进行注入,但是SpiExtensionInjector默认的行为是获取当前类型的自适应扩展点:
在这里插入图片描述
我们的扩展接口FrameWork 中并没有使用@Adaptive注解标注需要自适应扩展的接口方法,所以会因为找不到扩展标记点而抛出异常。

为了达到我们的预期,我们可以自定义一个CustomSpiExtensionInjector:

public class CustomSpiExtensionInjector implements ExtensionInjector {
    private ExtensionAccessor extensionAccessor;

    @Override
    public <T> T getInstance(Class<T> type, String name) {
        return extensionAccessor.getExtension(type,name);
    }

    @Override
    public void setExtensionAccessor(ExtensionAccessor extensionAccessor) {
        this.extensionAccessor = extensionAccessor;
    }
}

对应SPI文件:

customSpiExtensionInjector=com.adaptive.CustomSpiExtensionInjector

但是直接向上面这样写还是会存在问题,根本原因在于返回的ExtensionInjector集合中的顺序问题:
在这里插入图片描述
loader.getSupportedExtensions()方法返回的是经过字母表排序过的扩展类集合:
在这里插入图片描述
所以我们目前无法直接对ExtensionInjector进行排序,只能通过扩展实现类的别名来间接控制顺序。

为了防止我们自定义的ExtensionInjector把dubbo内部默认的依赖注入过程搅乱,需要通过注解打标记,限制我们自定义的ExtensionInjector所能处理的依赖注入范围:

public class CustomSpiExtensionInjector implements ExtensionInjector {
    private ExtensionAccessor extensionAccessor;

    @Override
    public <T> T getInstance(Class<T> type, String name) {
        if (!type.isAnnotationPresent(CustomInjector.class)) {
            return null;
        }
        return extensionAccessor.getExtension(type, name);
    }

    @Override
    public void setExtensionAccessor(ExtensionAccessor extensionAccessor) {
        this.extensionAccessor = extensionAccessor;
    }
}

在扩展类接口上打标记:

@CustomInjector
@SPI("spring")
public interface FrameWork {
    String getName(URL url);
    String getInfo();
}

再次运行测试用例,可以得到期望输出:
在这里插入图片描述
扩展依赖注入器(ExtensionInjector)集合无法自定义排序规则 #12402


Wrapper机制

wrapper机制核心代码就在扩展实例依赖注入处理过后,源码如下,我们来简单复习一下:

private T createExtension(String name, boolean wrap) {
        Class<?> clazz = getExtensionClasses().get(name);
        ...
        try {
            T instance = (T) extensionInstances.get(clazz);
            if (instance == null) {
                extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
                instance = (T) extensionInstances.get(clazz);
                instance = postProcessBeforeInitialization(instance, name);
                injectExtension(instance);
                instance = postProcessAfterInitialization(instance, name);
            }
            //和自适应扩展点创建的不同逻辑: 判断是否需要对当前扩展实例进行装饰
            if (wrap) {
                List<Class<?>> wrapperClassesList = new ArrayList<>();
                //当前扩展类相关wrapper类型搜集工作在getExtensionClasses中完成
                if (cachedWrapperClasses != null) {
                    wrapperClassesList.addAll(cachedWrapperClasses);
                    wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                    Collections.reverse(wrapperClassesList);
                }
                
                //wrapper class搜集是满足存在一个单参数的拷贝构造函数,并且参数类型为当前扩展类类型
                if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                    //不断循环,套娃创建一层层的装饰器对象
                    for (Class<?> wrapperClass : wrapperClassesList) {
                        //Wrapper注解用于实现按条件装饰
                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                        //如果wrapper class类上不存在Wrapper注解,那么表示装饰不需要满足任何条件
                        //否则,需要判断条件是否满足,满足才会进行装饰
                        boolean match = (wrapper == null) ||
                            ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
                                !ArrayUtils.contains(wrapper.mismatches(), name));
                        if (match) {
                            //满足则进入装饰流程
                            //1.实例化当前装饰类,采用的是单参的拷贝构造函数
                            //2.执行依赖注入流程
                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                            //3.执行后置处理流程
                            instance = postProcessAfterInitialization(instance, name);
                        }
                    }
                }
            }

            // Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
            //调用初始化接口---注意上面警告信息,也就是说经过包装后,我们的包装对象未必继承lifecycle接口,因此初始化调用也就不会发生了
            initExtension(instance);
            return instance;
        } ...
    }

在这里插入图片描述
这里简单说明一下装饰条件指的是什么:

  • 首先,如果某个扩展类型存在某个扩展实现,该扩展实现类中存在一个拷贝构造函数,类型为当前扩展类型,则该扩展实现类会被搜集作为当前扩展实现的wrapper装饰类
    在这里插入图片描述
  • 如果我们想限制当前wrapper对象只对满足条件的扩展实现类进行装饰,可以在wrapper对象类上标注@Wrapper注解,利用Wrapper注解中的属性作为装饰条件
@Retention(RetentionPolicy.RUNTIME)
public @interface Wrapper {

    /**
     * 只对扩展别名存在于matches数组中的扩展实现进行装饰
     */
    String[] matches() default {};

    /**
     * 如果扩展别名存在于matches数组中,则不会对当前扩展实现进行装饰
     */
    String[] mismatches() default {};

    /**
     * 用于扩展类型的多个wrapper实现类进行排序
     */
    int order() default 0;
}

使用实践

更改上面测试用例中扩展实现类:

@Wrapper(matches = "spring")
public class SpringBoot implements FrameWork{
    private FrameWork wrapper;

    public SpringBoot(FrameWork frameWork) {
        this.wrapper = frameWork;
    }

    @Override
    public String getName(URL url) {
        return "springBoot";
    }

    @Override
    public String getInfo() {
        return wrapper.getInfo()+" 自动化的SpringBoot框架";
    }
}

@Wrapper(matches = "springBoot")
public class Guice implements FrameWork{
    private FrameWork wrapper;

    public Guice(FrameWork frameWork) {
        this.wrapper = frameWork;
    }
    @Override
    public String getName(URL url) {
        return "guice";
    }

    @Override
    public String getInfo() {
        return wrapper.getInfo()+" google 开源的轻量级IOC框架";
    }
}

public class Spring implements FrameWork {
    @Override
    public String getName(URL url) {
        return "spring";
    }

    @Override
    public String getInfo() {
        return "流行的Spring框架";
    }
}

测试类:

        ApplicationModel applicationModel = ApplicationModel.defaultModel();
        ExtensionLoader<FrameWork> extensionLoader = applicationModel.getExtensionLoader(FrameWork.class);
        FrameWork frameWork = extensionLoader.getExtension("spring");
        System.out.println(frameWork.getInfo());

在这里插入图片描述
很明显,只有SpringBoot对Spring进行了装饰,而Guice没有对Spring进行装饰,因为其类上的@Wrapper注解限制了其只会对扩展别名为springBoot的扩展实现进行装饰。


注意

如果我们更改测试用例,尝试获取扩展别名为springBoot的扩展实现,则会抛出扩展不存在的异常:

        ApplicationModel applicationModel = ApplicationModel.defaultModel();
        ExtensionLoader<FrameWork> extensionLoader = applicationModel.getExtensionLoader(FrameWork.class);
        FrameWork frameWork = extensionLoader.getExtension("springBoot");
        System.out.println(frameWork.getInfo());

在这里插入图片描述
这个原因是FrameWork的Wrapper装饰类会被单独搜集起来,而不会作为普通扩展实现类保存起来:

loadClass方法是在dubbo加载当前扩展类型所有SPI文件流程中被调用的:(如有遗忘,回看前面两篇原理篇)

在这里插入图片描述
所以,当我们尝试从extensionClasses集合中获取别名为springBoot的普通扩展类型时,自然会找不到,而抛出异常。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/23746.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【JavaSE】Java基础语法(十九):接口新特性

文章目录 1. 接口组成更新概述2. 接口中默认方法3. 接口中静态方法4. 接口中私有方法 1. 接口组成更新概述 常量&#xff1a;接口可以定义全局常量&#xff0c;使用关键字public static final修饰。 抽象方法&#xff1a;接口中可以定义抽象方法&#xff0c;使用关键字public…

首发Yolov8优化:Adam该换了!斯坦福最新Sophia优化器,比Adam快2倍 | 2023.5月斯坦福最新成果

1.Sophia优化器介绍 斯坦福2023.5月发表的最新研究成果,他们提出了「一种叫Sophia的优化器,相比Adam,它在LLM上能够快2倍,可以大幅降低训练成本」。 论文:https://arxiv.org/pdf/2305.14342.pdf 本文介绍了一种新的模型预训练优化器:Sophia(Second-order Clippe…

<Linux开发>驱动开发 -之-基于pinctrl/gpio子系统的beep驱动

&#xff1c;Linux开发&#xff1e;驱动开发 -之-基于pinctrl/gpio子系统的beep驱动 交叉编译环境搭建&#xff1a; &#xff1c;Linux开发&#xff1e; linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下&#xff1a; &#xff1c;Linux开发&#xff1e; -之-系统移植…

完整卸载office以及重装office 2021

完整卸载office以及重装 一.背景 之前很早安装的word最近发现打开&#xff0c;编辑等操作都很卡&#xff0c;而且占用的CPU很多&#xff0c;20%左右&#xff0c;而在网上搜索了一些结果无法解决问题后&#xff0c;决定卸载重装 二. 卸载的建议方法 直接参考官方链接从PC卸载…

Pytest自动化测试框架之Allure报告

简介 Allure Framework是一种灵活的、轻量级、多语言测试报告工具。 不仅可以以简洁的网络报告形式非常简洁地显示已测试的内容&#xff0c; 而且还允许参与开发过程的每个人从日常执行中提取最大程度的有用信息和测试。 从开发/测试的角度来看&#xff1a; Allure报告可以…

弘基笔记本电脑怎么使用U盘重装系统?

弘基笔记本电脑怎么使用U盘重装系统&#xff1f;有的用户的弘基笔记本电脑使用过程中出现了蓝屏的情况&#xff0c;系统频繁的出现蓝屏问题导致自己的使用受到了影响&#xff0c;那么这个情况怎么去进行问题的解决呢&#xff1f;一起来看看以下的解决方法吧。 准备工作&#xf…

直接缓存访问DCA

直接缓存访问DCA&#xff1a;网卡原本DMA写是将接收到的数据帧写入系统内存&#xff0c;DCA机制是网卡DMA写输入的数据能直接发送到属于CPU内部的L2高速缓存中&#xff0c;从而提高网络IO的性能。 设备驱动程序要初始化网卡的DCA功能&#xff0c;将CPU ID号&#xff08;通过获取…

Unity-vr用眼睛注视选择物体

Unity-vr用眼睛注视选择物体 文章目录 Unity-vr用眼睛注视选择物体工程版本用法说明脚本说明WatchController - 注视主控制器WatchEvent - 注视事件WatchGameobject - 被注视物体TimerTool - 计时器工具 总结 工程版本 unity2019.4.9f1 vs2019 项目工程源代码下载 用法说明 …

技术大佬们都是怎么学习的?

目录 问题 熟悉更多业务 熟悉端到端 自学 Do exercise Learning trying Teaching 问题 今天逛帖子的时候&#xff0c;看到这么个问题&#xff1a; 这个问题我曾经也很好奇过&#xff0c;那些成为技术大佬的人当初是怎么学习&#xff0c;以及怎么成长过来的&#xff0…

私有GitLab仓库 - 本地搭建GitLab私有代码仓库并随时远程访问「内网穿透」

文章目录 前言1. 下载Gitlab2. 安装Gitlab3. 启动Gitlab4. 安装cpolar内网穿透5. 创建隧道配置访问地址6. 固定GitLab访问地址6.1 保留二级子域名6.2 配置二级子域名 7. 测试访问二级子域名 转载自远控源码文章&#xff1a;Linux搭建GitLab私有仓库&#xff0c;并内网穿透实现公…

Makefile

Makefile 1.举一个Makefile的例子2.使用变量3.更省事的方式,让Make 自动推导.PHONY:clean是什么意思&#xff1f;&#xff01; 4.cmake与Makefile的联系在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下&#xff1a; 1.举一个Makefile的例子 hello_demo : hellospea…

回收站中怎么找回误删除的文件?这几种方法很实用

当我们在电脑上操作文件的时候&#xff0c;难免会有不小心删除文件的情况发生。这个时候&#xff0c;我们可以打开回收站来找回误删除的文件。但是&#xff0c;有时候我们也会误将回收站清空。那么&#xff0c;该怎样才能找回已经误删除的文件呢&#xff1f;在这里提供了回收站…

chatgpt赋能python:用Python下载MP3的方法

用Python下载MP3的方法 如果你想从互联网上下载MP3&#xff0c;那么你可以使用Python来实现这个任务。在本文中&#xff0c;我们将介绍如何用Python编写程序来下载MP3&#xff0c;同时还将分享一些有用的工具和资源。 Python中使用的库 要下载MP3&#xff0c;你需要使用Pyth…

一篇文章搞定《Android事件分发》

一篇文章搞定《Android事件分发》 什么是事件分发MotionEvent事件事件如何从屏幕到APPInputManagerServiceWindowManagerServiceWindow小结 事件如何从APP到达对应页面第一步&#xff1a;分类第二步&#xff1a;送去Activity后续的传递小结&#xff1a; 页面的事件分发整个流程…

移动云COCA架构,重新定义下一代云

当前&#xff0c;算力已经成为 全球科技竞争的焦点 为此&#xff0c;移动云重磅发布 「移动云COCA&#xff08;Compute on chip Architecture&#xff09;软硬一体片上计算架构」 以下简称移动云COCA架构 以此打造国家级自主可控的 高性能算力底座 带动国产化智算产业成熟…

【ISO14229_UDS刷写】-5-$38诊断服务RequestFileTransfer理论部分

总目录&#xff1a;&#xff08;单击下方链接皆可跳转至专栏总目录&#xff09; 《UDS/OBD诊断需求编辑工具》总目录https://blog.csdn.net/qfmzhu/article/details/123697014 目录 1 $0x38 RequestFileTransfer诊断服务描述 2 0x38服务请求消息 2.1 0x38服务请求消息定义…

写给初学者的YOLO目标检测 概述

文章目录 什么是目标检测What is YOLO?为什么YOLO在目标检测领域如此流行&#xff1f;1. 速度快2. 高检测精度3. 更好的泛化性4. 开源 YOLO架构YOLO目标检测是如何工作的&#xff1f;残差块(Residual blocks)边界框回归&#xff08;Bounding box regression&#xff09;交并比…

基于ESP32-CAM 和 OpenCV 设计的手势控制虚拟鼠标

概述 在本文中,我们将使用ESP32-CAM和OpenCV开发手势控制虚拟鼠标。ESP32 Camera Module和Python程序可用于无线控制鼠标跟踪和点击操作。 入门者必须具备 Python、图像处理、嵌入式系统以及物联网的丰富知识。首先,我们将了解如何控制鼠标跟踪和单击,以及运行 python 程序…

20年前,微软给金山那刀,现今一举将WPS推上领奖台,WPS,赢了

WPS&#xff0c;赢了 正如姚冬&#xff08;金山办公副总裁&#xff09;所说&#xff1a; 文本的命令行到图形界面的那次&#xff0c;改变整个人机交互的方式&#xff0c;我们公司历史上最大的一次危机也是那一次&#xff0c; 但我们依然挺过来了&#xff0c;我们相信这次技术浪…

一个神奇的工具,让URL地址都变成了“ooooooooo“

一个神奇的工具&#xff0c;让URL地址都变成了"ooooooooo" 一、核心代码二、URL编码/解码 最近发现一个有意思工具&#xff0c;就是将一个URL地址转换为都是 ooooooooo 的样子&#xff0c;通过转换后的地址访问可以转换回到原始地址&#xff0c;转换的逻辑有点像短链…