【最新Dubbo3深入理解】Dubbo3中的SPI机制以及IOC、AOP

欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送!

在我后台回复 「资料」 可领取编程高频电子书
在我后台回复「面试」可领取硬核面试笔记

文章导读地址:点击查看文章导读!

感谢你的关注!

最新 Dubbo3 深入理解原理系列

在这里插入图片描述

Dubbo 的 SPI 机制

SPI 机制原理介绍

在 Dubbo 中 SPI 是一个非常重要的模块,基于 SPI 可以很容易的进行扩展,可以 很灵活的替换接口的实现类通过 SPI 可以在运行期间动态的寻找具体的实现类!

并且 Dubbo 的 SPI 还实现了自己的 IOC 和 AOP!

其实 SPI 的原理很简单,就是我们定义一个接口 UserService,在定义一个配置文件(假设为文件 a),此时假设 UserService 有两个实现类:UserServiceImpl1、UserServiceImpl2,用户根据自己的需求在文件 a 中指定需要加载哪一个实现类,如下:

# 指定接口对应实现类的全限定类名
com.example.hello.UserService=com.example.hello.impl.UserServiceImpl1

image-20240219140242325

像 Java 中也提供了 SPI 机制,但是 Dubbo 中并没有使用 Java 提供的 SPI ,而是 基于 Java 提供的 SPI 实现了一套功能更强的 SPI 机制!

Dubbo 中通过 SPI 指定实现类的配置文件放在 META-INF/dubbo 路径下(一般 SPI 机制的配置文件都在 META-INF 目录下)

Dubbo 为什么不用 JDK 中的 SPI 而是自己实现一套呢?

其实很容易想到,为什么不用呢,就是因为太弱了!

JDK 提供的 SPI 机制不满足 Dubbo 的需求,因此 Dubbo 才要开发自己的 SPI 机制

回答的思路就是,先说 JDK 的 SPI 哪里不满足呢?那就是列出 JDK 的 SPI 缺点

之后再说在 Dubbo 中的针对它的哪些需求做了哪些的改进

这些 JDK 的 SPI 缺点、Dubbo SPI 优点,网上一查一大堆,这里我也给列一下:

  • JDK SPI 的缺点:

JDK 的 SPI 机制在查找实现类的时候,由于配置文件根根据接口的全限定类名命名的,需要先遍历 META-INF/services/ 目录下的所有配置文件,找到对应的配置文件,再将配置文件中的全部实现类都取出来,进行实例化操作

因此呢,它的缺点就是无法按需加载实现类,导致出现资源浪费,并且指定了配置目录 META-INF/services/ ,不是很灵活

  • Dubbo SPI 的优点:

Dubbo 的 SPI 对配置文件的目录规定了多个,各自的职责不同:

  • META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI 。
  • META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件。
  • META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件。

Dubbo 的 SPI 代码中还实现了 IOC 和 AOP,可以对扩展的实现类进行依赖注入,以及 AOP 拦截,也就是方法增强

并且 Dubbo 中的 SPI 是通过 K-V 方式配置的,因此可以 按需加载实现类 ,优化了 JDK SPI 的缺点

从这几个点呢,可以看出 Dubbo 的 SPI 机制是非常灵活的,可以针对实现类做出拦截扩展操作,并且性能也不错,按需加载,不会出现资源浪费

Dubbo 中 SPI 使用

先说一下 Dubbo 中的 SPI 使用:

  • 第一步:配置文件如下(配置文件在 META-INF/dubbo 目录下,Dubbo 会自动去扫描该目录中的配置文件):
userServiceImpl1 = com.example.hello.impl.UserServiceImpl1
userServiceImpl2 = com.example.hello.impl.UserServiceImpl2
  • 第二步:SPI 接口:
@SPI
public interface UserService {
    void sayHello();
}
  • 第三步:加载实现类
public class DubboSPITest {
   @Test
   public void sayHello() throws Exception {
       ExtensionLoader<Robot> extensionLoader =
           ExtensionLoader.getExtensionLoader(UserService.class);
       UserService userServiceImpl1 = extensionLoader.getExtension("userServiceImpl1");
       userServiceImpl1.sayHello();
       UserService userServiceImpl2 = extensionLoader.getExtension("userServiceImpl2");
       userServiceImpl2.sayHello();
   }
}

Dubbo 的 SPI 实现中,包含了 IOC 和 AOP,接下来说一下 Dubbo 如何实现了 IOC 和 AOP

Dubbo 的 IOC?

Dubbo 通过 SPI 来创建接口的扩展实现类时,那么如果这个实现类中有其他扩展点的依赖的话,Dubbo 会自动将这些依赖注入到这个扩展实现类中

Dubbo 中的 IOC 和 AOP 的代码都是在 ExtensionLoader # createExtension() 方法中(为了代码简洁性,省略一些无关代码):

    @SuppressWarnings("unchecked")
    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);
                // IOC 代码
                injectExtension(instance);
                instance = postProcessAfterInitialization(instance, name);
            }

        }
    }

SPI 中 IOC 的核心方法就是 injectExtension()

    private T injectExtension(T instance) {
        try {
            // 使用反射遍历所有的方法
            for (Method method : instance.getClass().getMethods()) {
                // 如果不是 setter 方法就跳过
                if (!isSetter(method)) {
                    continue;
                }
                // 获取 setter 方法的参数
                Class<?> pt = method.getParameterTypes()[0];
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }

                try {
                    // 获取 setter 中需要设置的属性,比如 setUserName,该方法就是取出来 set 后边的名称 String property = "UserName"
                    String property = getSetterProperty(method);
                    // 寻找需要注入的属性
                    Object object = injector.getInstance(pt, property);
                    if (object != null) {
                        // 通过反射进行注入
                        method.invoke(instance, object);
                    }
                } 
            }
        } 
        return instance;
    }

Dubbo 的 IOC 是 通过 setter 方法注入依赖 的:

  • 第一步:通过反射获取实例的所有方法,找到 setter 方法
  • 第二步:通过 ObjectFactory(这里的 ObjectFactory 其实是 AdaptiveExtensionFactory 实例,这个实例就是 Dubbo 中的扩展工厂) 获取依赖对象(也就是需要注入的对象),来进行 setter 属性注入的!

Dubbo 的 AOP?

Dubbo 的 AOP 其实就是通过 装饰者模式 来实现的,在包装类上进行增强

Dubbo 的 IOC 和 AOP 都在 org.apache.dubbo.common.extension.ExtensionLoader # createExtension() 这个方法中,AOP 相关的源码如下:

 private T createExtension(String name, boolean wrap) {
        try {
            if (wrap) {
                List<Class<?>> wrapperClassesList = new ArrayList<>();
                // 拿到缓存中的包装类 WrapperClass
                if (cachedWrapperClasses != null) {
                    wrapperClassesList.addAll(cachedWrapperClasses);
                    // 将所有的包装类按照 order 进行排序,order 比较小的包装类在较外层
                    wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                    Collections.reverse(wrapperClassesList);
                }

                if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                    // 通过 for 循环,进行 Wrapper 的包装,进行包装类的层层嵌套
                    // 比如有三个 Wrapper 类,AWrapper、BWrapper、CWrapper
                    // 那么经过包装之后也就是:AWrapper(BWrapper(CWrapper(被包装类)))
                    // 执行流程:先执行 AWrapper 包装的方法,再执行 BWrapper 包装的方法,再执行 CWrapper 包装的方法,再执行被包装类的方法
                    for (Class<?> wrapperClass : wrapperClassesList) {
                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                        boolean match = (wrapper == null)
                                || ((ArrayUtils.isEmpty(wrapper.matches())
                                                || ArrayUtils.contains(wrapper.matches(), name))
                                        && !ArrayUtils.contains(wrapper.mismatches(), name));
                        if (match) {
                            // 先调用包装类的构造方法创建包装类,有 3 个包装类,因此是 3 次 for 循环,外层包装类包裹了里边的包装类
                            // 比如第一次就是 instance = CWrapper(被包装类)
                            // 第二次就是 instance = BWrapper(CWrapper(被包装类))
                            // 第三次就是 instance = AWrapper(BWrapper(CWrapper(被包装类)))
                            instance = injectExtension(
                                    (T) wrapperClass.getConstructor(type).newInstance(instance));
                        }
                    }
                }
            }
            return instance;
        } 
    }
}

上边的方法主要是扫描 wrapperClassesList(包装类),而这个包装类集合其实就是 cachedWrapperClasses

cachedWrapperClasses 是 Dubbo 在扫描类(执行 loadClass)的时候,会去判断这个类是不是包装类,如果是包装类,就加入到 cachedWrapperClasses 中

通过 for 循环进行包装类的包装,下边举一个 SPI AOP 的例子,也就是通过 Wrapper 包装实现 Dubbo 中的 AOP 机制

// Person 接口
@SPI("person")
public interface Person {
    void hello();
}
// SPI 接口实现类
public class Student implements Person {
    public void hello() {
        System.out.println("I am student");
    }
}
// Wrapper 包装类
public class StudentWrapper implements Person {
    private Person person;
    public StudentWrapper(Person person) {
        this.person = person;
    }
    public void hello() {
        System.out.println("before");
        person.hello();
        System.out.println("after");
    }
}
// Dubbo 配置文件(配置文件名与 Person 接口保持一致):resources/META-INF/dubbo/com.zqy.hello.Person
student=com.zqy.hello.impl.Student
filter=com.zqy.hello.wrapper.StudentWrapper
    
// 运行测试类即可看到包装类输出效果
public static void main(String[] args) {
    ExtensionLoader<Person> loader = ExtensionLoader.getExtensionLoader(Person.class);
    Person studesnt = loader.getExtension("student");
    studesnt.hello();
}

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

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

相关文章

MFC 配置Halcon

1.新建一个MFC 工程&#xff0c;Halcon 为64位&#xff0c;所以先将工程改为x64 > VC 目录设置包含目录和库目录 包含目录 库目录 c/c ->常规 链接器 ->常规 > 链接器输入 在窗口中添加头文件 #include "HalconCpp.h" #include "Halcon.h"…

canvas水波纹效果,jquery鼠标水波纹插件

canvas水波纹效果&#xff0c;jquery鼠标水波纹插件 效果展示 jQuery水波纹效果&#xff0c;canvas水波纹插件 HTML代码片段 <div class"scroll04wrap"><h3>发展历程</h3><div class"scroll04"><p>不要回头&#xff0c;一…

移动硬盘误删的文件还能找回来吗?1分钟弄清答案!

“想问问大家如果移动硬盘里保存了很多文件&#xff0c;但上次使用时不小心将部分文件删除了&#xff0c;还有机会找回误删的文件吗&#xff1f;应该怎么进行误删文件的恢复呢&#xff1f;” 在数字化时代&#xff0c;移动硬盘成为了我们存储和传输数据的重要工具。然而&#x…

外泌体相关基因肝癌临床模型预测——2-3分纯生信文章复现——02.数据格式整理(1)

内容如下&#xff1a; 1.外泌体和肝癌TCGA数据下载 2.数据格式整理 3.差异表达基因筛选 4.预后相关外泌体基因确定 5.拷贝数变异及突变图谱 6.外泌体基因功能注释 7.LASSO回归筛选外泌体预后模型 8.预后模型验证 9.预后模型鲁棒性分析 10.独立预后因素分析及与临床的…

【Appium UI自动化】pytest运行常见错误解决办法

通过Appium工具录制代码在pycharm上运行报错&#xff1a; 错误一&#xff1a; 1.提示 setup() 方法运行 error failed 解决办法&#xff1a;未创建 init __ 方法&#xff0c;创建一个空的__init.py文件就解决了。 原因&#xff1a; 错误二&#xff1a; 2.运行代码&#xff…

Rabbitmq入门与应用(四)-RabbitMQ常见模式

RabbitMQ常见Queue模式 简单模式 点对点模式&#xff0c;一个生产者一个消费者 生产者将消息发送到队列&#xff0c;消费者从队列中获取消息&#xff0c;队列是存储消息的缓冲区。 查看管理端效果 序列化解决方案 基于java序列化基于Json Bean public MessageConverter mess…

【Java从入门到精通】Java Character 类

Java Character 类 Character 类用于对单个字符进行操作。 Character 类在对象中包装一个基本类型 char 的值 实例 char ch a;// Unicode 字符表示形式 char uniChar \u039A; // 字符数组 char[] charArray { a, b, c, d, e }; 然而&#xff0c;在实际开发过程中&#xf…

利用Python email的MIMEBase及MIMEApplication发送邮件附件

**MIMEBase、 MIMEApplication背景知识点总结** MIMEBase 和 MIMEApplication 都是用于创建 MIME 消息的类&#xff0c;但它们之间有一些异同点。 异同点如下&#xff1a; 用途&#xff1a; MIMEBase 是一个基类&#xff0c;用于表示任何类型的 MIME 消息部分。它提供了一种通…

Video generation models as world simulators-视频生成模型作为世界模拟器

原文地址&#xff1a;Video generation models as world simulators 我们探索在视频数据上进行大规模生成模型的训练。具体来说&#xff0c;我们联合训练文本条件扩散模型&#xff0c;同时处理不同持续时间、分辨率和长宽比的视频和图像。我们利用一个在视频和图像潜在编码的时…

Redis信创平替之TongRDS(东方通),麒麟系统安装步骤

我的系统: 银河麒麟桌面系统V10(SP1)兆芯版 1.先进入东方通申请使用 2.客服会发送一个TongRDS包与center.lic给你(我这里只拿到.tar.gz文件,没有网上的什么安装版) 3.上传全部文件到目录中 4.服务节点安装,并启动 tar -zxvf TongRDS-2.2.1.2_P3.Node.tar.gz cd pmemdb/bin/…

c++实现栈和队列类

c实现栈和队列类 栈(Stack)Stack示意图Stack.cpp 队列(queue)queue 示意图queue.cpp 栈(Stack) Stack示意图 Stack.cpp #pragma once #include "ListStu.cpp"template<typename T> class Stack { public: /* * void push(T& tDate)* 参数一 &#xff1a;…

【Linux从青铜到王者】 基础IO

本篇重点&#xff1a;文件描述符&#xff0c;重定向&#xff0c;缓冲区&#xff0c;磁盘结构&#xff0c;文件系统&#xff0c;inode理解文件的增删查改&#xff0c;查找一个文件为什么一定要有路径&#xff0c;动静态库&#xff0c;有的时候为什么找不到库&#xff0c;动态库的…

SQL面试题及答案

介绍 在快节奏的数据管理和信息技术世界中,导航和操作结构化数据的能力是一项非常重要的技能。SQL,即结构化查询语言,是关系数据库的基石,掌握这种语言的专业人员的需求量很大。SQL 面试在科技行业很常见,潜在的候选人会接受测试以展示他们的知识和解决问题的能力。为了帮…

酷开科技丨新年新玩法!酷开系统壁纸模式给客厅“换”新

甲辰龙年即将到来&#xff0c;新年新家新气象&#xff0c;快到酷开系统壁纸模式中挑选一款喜欢的壁纸&#xff0c;为新的一年增添一份美好和喜悦吧&#xff01; 酷开科技将更多的电视新玩法带给你&#xff0c;让你的电视成为家庭中的焦点&#xff01;酷开系统壁纸模式&#xf…

【前端素材】推荐优质后台管理系统APP Zina平台模板(附源码)

一、需求分析 当我们从多个层次来详细分析后台管理系统时&#xff0c;可以将其功能和定义进一步细分&#xff0c;以便更好地理解其在不同方面的作用和实际运作。 1. 功能层次 a. 用户管理功能&#xff1a; 用户注册和登录&#xff1a;管理用户账户的注册和登录过程。权限管…

easyui 手风琴Accordion 面板的高度设置

今天接到一个新的小需求&#xff0c;如下图&#xff0c;当预算表单只有一个时&#xff0c;要求不显示预算表单这块的内容。 考虑到页面创建时用到了表单的回调和点击方法&#xff0c;所以不能单纯的移除&#xff0c;移除右侧表格的创建会报错&#xff0c;所以只能隐藏。 隐藏…

突破挑战:利用沃尔玛跨境智星实现批量注册与下单

在进行沃尔玛测评时&#xff0c;确保环境安全至关重要。每个账号都应使用独立的运行环境&#xff0c;这样可以避免浏览器指纹、字体以及浏览器数据之间的关联问题。此外&#xff0c;账号的资料也需要特别注意。最好是自己注册自己下单&#xff0c;这样可以确保所有资源都掌握在…

JAVA工程师面试专题-并发编程篇

目录 一、线程 1、并发与并行的区别 2、同步和异步的区别 3、Java中创建线程有哪些方式? 4、Thread和Runnable的区别 5、Java中的Runnable、Callable、Future、FutureTask的区别和联系&#xff1f; 6、说一下你对 CompletableFuture 的理解 7、volatile关键字有什么用&…

如何下载 VisualStudio2022 离线包

1.概要&#xff0c;有些场景下&#xff0c;不允许联网&#xff0c;需要下载离线安装包。 2.过程 2.1 下载地址 创建基于网络的安装 - Visual Studio (Windows) | Microsoft Learn 2.2 选择引导程序 2.3 执行命令 2.3.1 下载全部&#xff1a; vs_Enterprise.exe --layout …

芯科科技与Arduino携手推动Matter普及化

双方的合作可助力开发人员在两分钟内将新开发板配置入网 致力于以安全、智能无线连接技术&#xff0c;建立更互联世界的全球领导厂商Silicon Labs&#xff08;亦称“芯科科技”&#xff0c;NASDAQ&#xff1a;SLAB&#xff09;日前宣布&#xff0c;公司与开源硬件和软件领域的…