【最新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 {@Testpublic 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<>();// 拿到缓存中的包装类 WrapperClassif (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();
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QT-串口工具

一、演示效果 二、关键程序 &#xff1a; #include "mainwindow.h" #include "ui_mainwindow.h"#include <QMessageBox>MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow),listPlugins(QList<TabPluginInt…

【Git企业实战开发】Git常用开发流操作总结

【Git企业实战开发】Git常用开发流操作总结 大家好 我是寸铁&#x1f44a; 总结了一篇Git常用开发流操作总结的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 现在刚做项目的伙伴&#xff0c;可能你之前学过git&#xff0c;但是一实战发现不熟悉 没关系&#xff0c;看寸铁这篇…

【Maven】介绍、下载及安装、集成IDEA

目录 一、什么是Maven Maven的作用 Maven模型 Maven仓库 二、下载及安装 三、IDEA集成Maven 1、POM配置详解 2、配置Maven环境 局部配置 全局设置 四、创建Maven项目 五、Maven坐标详解 六、导入Maven项目 方式1&#xff1a;使用Maven面板&#xff0c;快速导入项目 …

Oladance、南卡、韶音开放式耳机怎么样?3个月真实对比测评

​哪款开放式耳机好用&#xff1f;我亲自体验并评测了市场上流行的三个品牌的开放式耳机&#xff1a;Oladance、南卡、韶音。通过深入测试多维度性能表现&#xff0c;确保你能够远离劣质产品可能带来的问题。我想提醒大家&#xff0c;如果选错耳机可能会影响到音乐的真实还原和…

嵌入式学习-qt-Day3

嵌入式学习-qt-Day3 一、思维导图 二、作业 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳…

放大电路静态工作点的稳定

什么对Q点有影响&#xff1f; 温度、电源&#xff08;VCC&#xff09;的波动、元器件的老化等。 对于电源的波动&#xff0c;可以用好的电源来抑制&#xff0c;使波动变得小&#xff1b;对于元器件的老化&#xff0c;可以在出厂前老化一下&#xff0c;后面可以稳定用很长时间…

协程源码 launch 流程跟踪学习

为了更深入学习协程的底层实现原理&#xff0c;了解协程线程切换的根本本质。也为了以后在工作中可以根据不同的需求场景&#xff0c;更加随心所欲的使用不同的协程。 今天通过 launch 跟踪一下协程的执行流程。 fun getData() {Trace.beginSection("getData");Log.…

vue3中使用vuedraggable实现拖拽el-tree数据进分组

看效果&#xff1a; 可以实现单个拖拽、双击添加、按住ctrl键实现多个添加&#xff0c;或者按住shift键实现范围添加&#xff0c;添加到框中的数据&#xff0c;还能拖拽排序 先安装 vuedraggable 这是他的官网 vue.draggable中文文档 - itxst.com npm i vuedraggable -S 直接…

构建高效稳定的外卖平台架构设计与实现

外卖行业的快速发展为人们的生活带来了便利&#xff0c;随着外卖市场的扩大和竞争的加剧&#xff0c;外卖平台的架构设计变得至关重要。一个高效稳定的架构可以支持平台的快速发展&#xff0c;提供优质的服务体验&#xff0c;同时保障用户数据的安全性。 用户端架构设计 移动端…

11.CSS3的媒介(media)查询

CSS3 的媒介(media)查询 经典真题 如何使用媒体查询实现视口宽度大于 320px 小于 640px 时 div 元素宽度变成 30% 媒体查询 媒体查询英文全称 Media Query&#xff0c;顾名思义就是会查询用户所使用的媒体或者媒介。 在现在&#xff0c;网页的浏览终端是越来越多了。用户可…

【elementUi-table表格】 滚动条 新增监听事件; 滚动条滑动到指定位置;

1、给滚动条增加监听 this.dom this.$refs.tableRef.bodyWrapperthis.dom.scrollTop 0let _that thisthis.dom.addEventListener(scroll, () > {//获取元素的滚动距离let scrollTop _that.dom.scrollTop//获取元素可视区域的高度let clientHeight this.dom.clientHeigh…

vue实现拖拽(vuedraggable)

实现效果: 左侧往右侧拖动&#xff0c;右侧列表可以进行拖拽排序。 安装引用&#xff1a; npm install vuedraggable import draggable from vuedraggable 使用&#xff1a; data数据&#xff1a; componentList: [{groupName: 考试题型,children: [{componentType: danxua…