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

news2024/11/20 7:24:27

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();
}

再次运行测试用例,可以得到期望输出:
在这里插入图片描述


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的普通扩展类型时,自然会找不到,而抛出异常。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM8、STM8S003F3P6 双串口通信(IO模拟串口)

背景 这里为什么要写串口通信&#xff0c;因为实际项目上使用了串口&#xff0c;STM8S003F3P6的串口简单啊&#xff0c;不值得一提。本文写的串口确实简单&#xff0c;因为这里我想先从简单的写起来&#xff0c;慢慢的把难的引出来。这里呢&#xff0c;做个提纲说明&#xff0c…

VB一款实现图像浏览的ActiveX控件

利用GDI实现浏览图片的ActiveX控件&#xff0c;功能:支持读取PNG格式的图片&#xff0c;支持鼠标飞轮对图片进行缩放&#xff0c;镜像&#xff0c;移动等功能&#xff0c;其中用到了功能强大的GDI&#xff0c;GDI&#xff0c;对初及vb编程爱好者运用GDI-API有很大的研究价值&am…

超级简单的SSM框架(全注解,源码+分析,看一眼就会)

1.什么是SSM&#xff1f; SSM是Spring、SpringMVC、Mybatis的框架整合。 2.什么是Spring? Spring是一个轻量级的控制反转&#xff08;IoC&#xff09;和面向切面&#xff08;AOP&#xff09;的容器框架。 优点&#xff1a; 1.通过Spring的IOC特性&#xff0c;将对象之间的…

基于关系抽取的相似度计算

文章目录 一、面向冶金设备运维履历的知识图谱构建与语义相似性度量研究二、KG中的实体相似度计算研究研究假设研究方法第一步&#xff1a;特征生成第二步&#xff1a;模型选择 三、基于司法案例知识图谱的类案推荐个人解惑 一、面向冶金设备运维履历的知识图谱构建与语义相似性…

叮咚买菜业绩大幅低于预期,2023年前景堪忧

来源l&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 叮咚买菜2023年第一季度业绩低于预期 叮咚买菜&#xff08;DDL&#xff09;于5月12日公布了2023年第一季度财报。 财报显示&#xff0c;叮咚买菜的收入同比下降了-8%&#xff0c;从2022年第一季度的54.44亿元人民币下滑…

搭建Stm32遇到的问题汇总

问题一&#xff1a;右侧watch窗口无法显示数据动态变化 现象&#xff1a;在main循环中加入累加的数值都不变化 现象2&#xff1a;单步执行卡在如下函数 原因&#xff1a;一般是因为没有用微库进入半主机模式 解决措施&#xff1a; 措施一&#xff1a;打开勾选下面的。 措施二…

【HTTP】

目录 &#x1f3a8;1. HTTP 请求 &#x1f3f0;1.1 首行 &#x1f451;1.2 URL &#x1f6a9;1.3 Content-Type 和 Content-Length &#x1f34a;1.4 User-Agent &#x1f33d;1.5 Referer ⚽1.6 Cookie &#x1f369;2. HTTP 响应 &#x1f31e;2.1 HTTP 响应 首行…

软件工程学习1:软件项目项目管理

假设你被指派作为一个软件公司的项目负责人&#xff0c;任务是开发一个管理系统&#xff0c;人员规模大约为8人&#xff0c;人员角色应如何确定&#xff1f;应如何进行项目管理&#xff1f;&#xff08;从软件项目管理估算、软件规模、工作量估算、进度计划安排、软件过程模型等…

入行4年,跳槽2次,我好像摸透了软件测试这一行!

很多测试人在行业中摸爬滚打了很多年&#xff0c;时不时给身边新入职的伙伴们一些好的建议&#xff0c;对一部分刚入职起步的测试小白来说&#xff0c;这些建议都是让你少走弯路的捷径&#xff0c;废话不多说&#xff0c;让我们来了解一下&#xff0c;一位入行4年跳槽2次的老测…

JSONException: illegal identifier : \pos 1 异常报错问题

JSONException: illegal identifier : \pos 1 异常报错问题 1.常见情况&#xff1a;1.1 JSON 字符串格式不正确1.2 JSON 字符串中包含了非法字符1.3 解析 JSON 字符串的方式不正确 2.解决办法&#xff1a;2.1 工具类2.2 StringEscapeUtils.unescapeJava3. JSONObject.parseObje…

黑客入门教程从零基础入门到精通,看完这一篇就够了

学前感言: 1.这是一条坚持的道路,三分钟的热情可以放弃往下看了. 2.多练多想,不要离开了教程什么都不会了.最好看完教程自己独立完成技术方面的开发. 3.有时多google,baidu,我们往往都遇不到好心的大神,谁会无聊天天给你做解答. 4.遇到实在搞不懂的,可以先放放,以后再来解决…

Revit建模|怎么创建轴网标高?

大家好&#xff0c;这里是建模助手&#xff0c;今天给大家讲一讲怎么创建轴网标高。 标高用来定义楼层层高以及生成平面视图&#xff0c;轴网用于为构件定位&#xff0c;在Revit中轴网确定了一个不可见的工作平面&#xff0c;轴网编号以及标高符号样式均可定制修改。目前&…

每日练题---C语言

目录 前言&#xff1a; 一.求最小公倍数 1.1公式法 1.2遍历法 1.3乘除法 二.倒置字符串 前言&#xff1a; 今日份题目有&#xff1a;求两个整数的最小公倍数&#xff0c;求倒置字符串&#xff0c;。 一.求最小公倍数 牛客网链接&#xff1a;OJ链接 百度词条&#xff1a;…

Python自动化测试框架怎么搭建?完整框架源码给到你

目录 前言 搭建过程&#xff1a; 一阶段&#xff0c; 二阶段&#xff0c; 三阶段&#xff0c; 四阶段 下面具体的说一下搭建过程 一阶段&#xff1a; 二阶段&#xff1a; 三阶段&#xff1a; 四阶段 前言 背景&#xff1a;公司需要每一个项目组都搭建自己的一套自动…

Vue企业级项目开发思路,附带源码

项目的技术栈展示 以及项目的核心重点部分 项目搭建使用element实现首页布局 顶部导航菜单及与左侧导航联动的面包屑实现 封装一个ECharts组件 封装一个Form表单组件和Table表格组件 企业开发之权限管理思路讲解 项目搭建使用element实现首页布局 顶部导航菜单及与左侧导…

技术分享 | OB 慢查询排查思路

本文汇总了项目实践中前辈的经验和笔者的理解&#xff0c;旨在帮助初学 OceanBase&#xff08;以下简称 OB&#xff09;的工程师&#xff0c;快速解决 SQL 执行缓慢等性能问题。当遇到性能问题时&#xff0c;很多工程师可能会感到无从下手&#xff0c;本文将根据关键日志提供多…

14_Uboot图形化配置

目录 U-Boot图形化配置体验 make menuconfig过程分析 Kconfig语法简介 Mainmenu menu/endmenu条目 config条目 depends on和select choice/endchoice Menuconfig Comment Source 添加自定义菜单 U-Boot图形化配置体验 uboot或Linux内核可以通过输入"make menu…

计算机组成原理-存储系统-外部存储虚拟存储器

目录 一、外部存储 1.1磁盘组成 1.2性能指标 1.3磁盘地址 1.4硬盘的工作原理 1.5磁盘阵列 二、 固态硬盘SSD 三、虚拟存储器(存储系统详细知识点) 3.1 页式存储器 逻辑地址-》主存(物理)地址 加入块表(TLB)的转换过程 3.2 段式存储器 3.3 段页式存储器 一、外部存储 又称…

一个非系统工程师所关心的——Android开机流程

一、Loader层 1. Boot ROM: 上电后&#xff0c;BootRom会被激活&#xff0c;引导芯片代码开始从预定义的地方&#xff08;固化在ROM&#xff09;开始执行&#xff0c;然后加载引导程序到RAM。 2. Boot Loader引导程序 Android是基于Linux系统的&#xff0c;它没有BIO…

路由器端口映射-原理+图解

文章目录 1. 前言2. 内部服务器3. 内网IP3.1 含义3.2 查询内网IP方法3.3 直观法判断内网IP 4. 内部端口5. 外部端口6. 远程桌面连接7. 端口映射原理图8. 欢迎纠正~ 1. 前言 端口映射就是可将N台主机的内网IP地址映射成一个公网IP地址&#xff0c;从而让外网可以访问到局域网内…