3.0 Dubbo的可扩展机制SPI源码解析

news2025/4/17 18:36:21

1. Dubbo SPI 架构图

2. Demo

ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExt ensionLoader(Protocol.class);
Protocol http = extensionLoader.getExtension("dubbo");
System.out.println(http);

DemoDubbo⻅的写示获"dubbo"Protocol点。Protocol⼀个接口。

ExtensionLoader的内部有⼀个staticConcurrentHashMap来缓所对应的

2.1 ExtensionLoader实例

ExtensionLoader

ExtensionLoader示某接⼝的载器载某例。

ExtensionLoader开有⽂的staticMap还有两个重要的属

  1. Class<?> type示当ExtensionLoader例是哪接⼝的载器
  2. ExtensionFactory objectFactory⼯⼚对象⼯⼚),以获得某对象

ExtensionLoaderExtensionFactory的区别在于

  1. ExtensionLoader最终所得到的对象Dubbo SPI⽣的
  2. ExtensionFactory最终所得到的对象可能Dubbo SPI所产⽣的可能是从Spring器中所获的对象

ExtensionLoader中有三个⽤的⽅

  1. getExtension("dubbo")示获取名字dubbo
  2. getAdaptiveExtension()示获⼀个适应
  3. getActivateExtension(URL url, String[] values, String group)⼀个url激活后⽂详解释
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class)

当我们调⽤上述代码,我们会将得到⼀个DubboProtocol的实例对象,

但在getExtension()⽅法中,Dubbo会对DubboProtocol对象进⾏依赖注⼊(也就是⾃动给属性赋值,属性的类型为⼀个接⼝,记为A 接⼝),

这个时候,对于Dubbo来说它并不知道该给这个属性赋什么值,换句话说,Dubbo并不知道在进⾏依赖注⼊时该找⼀个什么的的扩展点对象给这个属性,这时就会预先赋值⼀个A接⼝的⾃适应扩展点实例,也就是A接⼝的⼀个代理对象。后续,在A接⼝的代理对象被真正⽤到时,才会结合URL信息找到真正的A接⼝对应的扩展点实例进⾏调⽤。

getExtension(String name)⽅法
在调⽤getExtension去获取⼀个扩展点实例后,会对实例进⾏缓存,下次再获取同样名字的扩展点实例时  就会从缓存中拿了。

createExtension(String name)⽅法
在调⽤createExtension(String name)⽅法去创建⼀个扩展点实例时,要经过以下⼏个步骤:
1.根据name找到对应的扩展点实现类
2.根据实现类⽣成⼀个实例,把实现类和对应⽣成的实例进⾏缓存
3.对⽣成出来的实例进⾏依赖注⼊(给实例的属性进⾏赋值)
4.对依赖注⼊后的实例进⾏AOP(Wrapper),把当前接⼝类的所有的Wrapper全部⼀层⼀层包裹在实例对象上,没包裹个Wrapper后,也会对Wrapper对象进⾏依赖注⼊
5.返回最终的Wrapper对象

getExtensionClasses
getExtensionClasses()是⽤来加载当前接⼝所有的扩展点实现类的,返回⼀个Map。之后可以从这个Map中按照指定的name获取对应的扩展点实现类。
当把当前接⼝的所有扩展点实现类都加载出来后也会进⾏缓存,下次需要加载时直接拿缓存中的。
Dubbo在加载⼀个接⼝的扩展点时,思路是这样的:
1.根据接⼝的全限定名去META-INF/dubbo/internal/⽬录下寻找对应的⽂件,调⽤loadResource⽅法
进⾏加载
2.根据接⼝的全限定名去META-INF/dubbo/⽬录下寻找对应的⽂件,调⽤loadResource⽅法进⾏加载
3.根据接⼝的全限定名去META-INF/services/⽬录下寻找对应的⽂件,调⽤loadResource⽅法进⾏加载

这⾥其实会设计到⽼版本兼容的逻辑,不解释了。

loadResource⽅法
loadResource⽅法就是完成对⽂件内容的解析,按⾏进⾏解析,会解析出"="两边的内容,"="左边的内容  就是扩展点的name,右边的内容就是扩展点实现类,并且会利⽤ExtensionLoader类的类加载器来加载扩展点实现类。
然后调⽤loadClass⽅法对name和扩展点实例进⾏详细的解析,并且最终把他们放到Map中去。
 

loadClass⽅法
loadClass⽅法会做如下⼏件事情:
1.当前扩展点实现类上是否存在@Adaptive注解,如果存在则把该类认为是当前接⼝的默认⾃适应类(接
⼝代理类),并把该类存到cachedAdaptiveClass属性上。
2.当前扩展点实现是否是⼀个当前接⼝的⼀个Wrapper类,如果判断的?就是看当前类中是否存在⼀个构 造⽅法,该构造⽅法只有⼀个参数,参数类型为接⼝类型,如果存在这⼀的构造⽅法,那么这个类就是   该接⼝的Wrapper类,如果是,则把该类添加到cachedWrapperClasses中去,
cachedWrapperClasses是⼀个set。
3.如果不是⾃适应类,或者也不是Wrapper类,则判断是有存在name,如果没有name,则报错。
4.如果有多个name,则判断⼀下当前扩展点实现类上是否存在@Activate注解,如果存在,则把该类添加到cachedActivates中,cachedWrapperClasses是⼀个map。
5.最后,遍历多个name,把每个name和对应的实现类存到extensionClasses中去,extensionClasses  就是上⽂所提到的map。
⾄此,加载类就⾛完了。
回到createExtension(String name)⽅法中的逻辑,当前这个接⼝的所有扩展点实现类都扫描完了之后, 就可以根据⽤户所指定的名字,找到对应的实现类了,然后进⾏实例化,然后进⾏IOC(依赖注⼊)和AOP。

3. Dubbo中的IOC

1.根据当前实例的类,找到这个类中的setter⽅法,进⾏依赖注⼊
2.先分析出setter⽅法的参数类型pt
3.在截取出setter⽅法所对应的属性名property
4.调⽤objectFactory.getExtension(pt, property)得到⼀个对象,这⾥就会从Spring容器或通过
DubboSpi机制得到⼀个对象,⽐较特殊的是,如果是通过DubboSpi机制得到的对象,是pt这个类型的⼀个⾃适应对象(代理对象)。
5.再反射调⽤setter⽅法进⾏注⼊

4. Dubbo中的AOP

dubbo中也实现了⼀套⾮常简单的AOP,就是利⽤Wrapper,
如果⼀个接⼝的扩展点中包含了多个Wrapper类,那么在实例化完某个扩展点后,
就会利⽤这些Wrapper类对这个实例进⾏包裹,
⽐如:现在有⼀个DubboProtocol的实例,同时对于Protocol这个接⼝还有很多的Wrapper,
⽐如ProtocolFilterWrapper、ProtocolListenerWrapper,那么,当对DubboProtocol的实例完成了IOC之后,就会先调⽤new ProtocolFilterWrapper(DubboProtocol实例)⽣成⼀个新的Protocol的实例,再对此实例进⾏IOC,完了之后,会再调⽤new ProtocolListenerWrapper(ProtocolFilterWrapper实例)⽣成
⼀个新的Protocol的实例,然后进⾏IOC,从⽽完成DubboProtocol实例的AOP。

5. ⾃适应扩展点补充

上⾯提到的⾃适应扩展点对象,也就是某个接⼝的代理对象是通过Dubbo内部⽣成代理类,然后⽣成代理   对象的。

额外的,在Dubbo中还设计另外⼀种机制来⽣成⾃适应扩展点,这种机制就是可以通过@Adaptive注解来 指定某个类为某个接⼝的代理类,如果指定了,Dubbo在⽣成⾃适应扩展点对象时实际上⽣成的就是
@Adaptive注解所注解的类的实例对象。

如果是由Dubbo默认实现的,那么我们就看看Dubbo是如何⽣成代理类的。

createAdaptiveExtensionClass⽅法
createAdaptiveExtensionClass⽅法就是Dubbo中默认⽣成Adaptive类实例的逻辑。说⽩了,这个实例就是当前这个接⼝的⼀个代理对象。⽐如下⾯的代码:

ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol protocol = extensionLoader.getAdaptiveExtension();

这个代码就是Protocol接⼝的⼀个代理对象,那么代理逻辑就是在new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate()⽅法中。

1.type就是接⼝
2.cacheDefaultName就是该接⼝默认的扩展点实现的名字

看个例子,Protocol接口的Adaptive类:

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    
	public void destroy()  {
		throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
	}

    public int getDefaultPort()  {
		throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
	}
    
	public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
		if (arg0 == null) 
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
		if (arg0.getUrl() == null) 
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
		
        org.apache.dubbo.common.URL url = arg0.getUrl();
		
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );

        if(extName == null) 
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
 		
        return extension.export(arg0);
	}

    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {

        if (arg1 == null) throw new IllegalArgumentException("url == null");

        org.apache.dubbo.common.URL url = arg1;

        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );

        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");

        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);

        return extension.refer(arg0, arg1);
	}
}

可以看到,Protocol接口中有四个方法,但是只有export和refer两个方法进行代理。为什么?因为Protocol接口中在export方法和refer方法上加了@Adaptive注解。但是,不是只要在方法上加了@Adaptive注解就可以进行代理,还有其他条件,比如:

  1. 该方法如果是无参的,那么则会报错
  2. 该方法有参数,可以有多个,并且其中某个参数类型是URL,那么则可以进行代理
  3. 该方法有参数,可以有多个,但是没有URL类型的参数,那么则不能进行代理
  4. 该方法有参数,可以有多个,没有URL类型的参数,但是如果这些参数类型,对应的类中存在getUrl方法(返回值类型为URL),那么也可以进行代理

所以,可以发现,某个接口的Adaptive对象,在调用某个方法时,是通过该方法中的URL参数,通过调用ExtensionLoader.getExtensionLoader(com.luban.Car.class).getExtension(extName);得到一个扩展点实例,然后调用该实例对应的方法。

Activate扩展点

上文说到,每个扩展点都有一个name,通过这个name可以获得该name对应的扩展点实例,但是有的场景下,希望一次性获得多个扩展点实例

demo

ExtensionLoader<Filter> extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);
URL url = new URL("http://", "localhost", 8080);
url = url.addParameter("cache", "test");

List<Filter> activateExtensions = extensionLoader.getActivateExtension(url, 
                                                      new String[]{"validation"},
                                                      CommonConstants.CONSUMER);
for (Filter activateExtension : activateExtensions) {
	System.out.println(activateExtension);
}

会找到5个Filter

org.apache.dubbo.rpc.filter.ConsumerContextFilter@4566e5bd
org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter@1ed4004b
org.apache.dubbo.monitor.support.MonitorFilter@ff5b51f
org.apache.dubbo.cache.filter.CacheFilter@25bbe1b6
org.apache.dubbo.validation.filter.ValidationFilter@5702b3b1

前三个是通过CommonConstants.CONSUMER找到的
CacheFilter是通过url中的参数找到的
ValidationFilter是通过指定的name找到的


在一个扩展点类上,可以添加@Activate注解,这个注解的属性有:
1String[] group():表示这个扩展点是属于哪组的,这里组通常分为PROVIDER和CONSUMER,表示该扩展点能在服务提供者端,或者消费端使用
2String[] value():表示的是URL中的某个参数key,当利用getActivateExtension方法来寻找扩展点时,如果传入的url中包含的参数的所有key中,包括了当前扩展点中的value值,那么则表示当前url可以使用该扩展点。




 

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

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

相关文章

java八股文面试[多线程]——newWorkStealingPool

newWorkStealingPool是什么&#xff1f; newWorkStealingPool简单翻译是任务窃取线程池。 newWorkStealingPool 是Java8添加的线程池。和别的4种不同&#xff0c;它用的是ForkJoinPool。 使用ForkJoinPool的好处是&#xff0c;把1个任务拆分成多个“小任务”&#xff0c;把这…

【MySQL学习笔记】(七)内置函数

内置函数 日期函数示例案例-1案例-2 字符串函数示例 数学函数其他函数 日期函数 示例 获得当前年月日 mysql> select current_date(); ---------------- | current_date() | ---------------- | 2023-09-03 | ---------------- 1 row in set (0.00 sec)获得当前时分秒…

剑指 Offer 57 - II. 和为s的连续正数序列(简单)

题目&#xff1a; class Solution { public:vector<vector<int>> findContinuousSequence(int target) { //本题使用滑动窗口&#xff08;双指针&#xff09;int i1, j1; //定义左右边界&#xff0c;一般是左闭右开int sum0; //窗口内的和vector&…

区块链技术与应用 - 学习笔记1【引言】

大家好&#xff0c;我是比特桃。本系列主要将我之前学习区块链技术时所做的笔记&#xff0c;进行统一的优化及整合。其中大量笔记源于视频课程&#xff1a;北京大学肖臻老师《区块链技术与应用》公开课。肖老师的课让我找回了求知若渴般的感觉&#xff0c;非常享受学习这门课的…

2023/9/3周报

目录 摘要 文献阅读1 1、标题和提出问题 2、物理模型对于水质预测的缺陷 3、模型框架 4、相关公式 5、结果分析 文献阅读2 1、标题和提出问题 2、问题叙述 3、模型框架 4、误差修补 5、实验结果和分析 总结 摘要 本周阅读了2篇论文&#xff0c;分别为一种基于深…

ReactNative 井字游戏 实战

效果展示 需要的插件准备 此实战项目需要用到两个插件。 react-native-snackbar 底部信息提示组件。 react-native-vector-icons 图标组件。 安装组件&#xff1a; npm i react-native-snackbar npm i react-native-vector-icons npm i types/react-native-vector-icons /…

uni-app之android离线自定义基座

一 为什么要自定义基座 1&#xff0c;基座其实就是一个app&#xff0c;然后新开发的页面可以直接在手机上面显示&#xff0c;查看效果。 2&#xff0c;默认的基座就是uniapp帮我们打包好的基座app&#xff0c;然后我们可以进行页面的调试。 3&#xff0c;自定义基座主要用来…

Python:列表推导式

相关阅读 Python专栏https://blog.csdn.net/weixin_45791458/category_12403403.html?spm1001.2014.3001.5482 列表推导式使得创建特定列表的方式更简洁。常见的用法为&#xff0c;对序列或可迭代对象中的每个元素应用某种操作&#xff0c;用生成的结果创建新的列表&#xff…

VSC++: 奇怪的风吹

void 奇怪的风吹() {//缘由https://ask.csdn.net/questions/1062454int aa[]{15, 30, 12, 36, 11, 20, 19, 17, 16, 18, 38, 15, 30, 12, 36, 11, 20, 19, 17, 16, 18, 38, -1},j 0, a 0, y 0, z 0;while (aa[j] > 0){if (j && aa[j] > 35 || aa[j] < 15)…

CXL.cache D2H Message 释义

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

记录一下自己对linux分区挂载的理解

一直狠模糊&#xff0c;分两个区&#xff0c;一个挂载/, 一个挂载/home 两者是什么关系 实测 先看挂载的内容 然后umount /home后创建一个新文件 再挂载回去 发现旧分区又回来了&#xff0c;说明路径只是个抽象的概念&#xff0c;分区挂载&#xff0c;互相之间数据是不影响…

只有一个线程的程序(main函数)

C并发编程入门 目录 只有一个线程的程序 就是main函数所在的线程。 这个线程无需我们手动创建&#xff0c;main函数就是这个线程的执行体。 运行main函数&#xff0c;就是执行只有一个线程的程序。 线程是进程内正在执行的代码所在的函数 代码示例 #include <iostream&…

弹性盒子的使用

一、定义 弹性盒子是一种用于按照布局元素的一维布局方法&#xff0c;它可以简便、完整、响应式地实现各种页面布局。 容器中存在两条轴&#xff0c;主轴和交叉轴(相当于我们坐标轴的x轴和y轴)。我们可以通过flex-direction来决定主轴的方向。 主轴&#xff08;main axis&am…

CentOS 7.6源码安装gdb 12.1

参考文章&#xff1a;《GDB调试-从安装到使用》 gdb --version看一下当前gdb的版本&#xff0c;可以看到是7.6.1-120.el7。 https://www.sourceware.org/gdb/download/可以下载gdb源码。 sudo nohup wget https://sourceware.org/pub/gdb/releases/gdb-12.1.tar.xz &下…

CXL.cache H2D Message 释义

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

CXL Memory Cache 分类及 Cacheline 归属问题

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

【原创】鲲鹏ARM构架openEuler操作系统安装Oracle 19c

作者:einyboy 【原创】鲲鹏ARM构架openEuler操作系统安装Oracle 19c | 云非云计算机科学、自然科学技术科谱http://www.nclound.com/index.php/2023/09/03/%E3%80%90%E5%8E%9F%E5%88%9B%E3%80%91%E9%B2%B2%E9%B9%8Farm%E6%9E%84%E6%9E%B6openeuler%E6%93%8D%E4%BD%9C%E7%B3%BB%…

进程、操作系统

文章目录 一、冯诺依曼体系&#xff08;Von Neumann Architecture&#xff09;1. 概述2. CPU 二、操作系统&#xff08;Operating System&#xff09;三、进程(process)/任务(task) 一、冯诺依曼体系&#xff08;Von Neumann Architecture&#xff09; 1. 概述 分类 CPU 中央处…

自建音乐播放器之一

这里写自定义目录标题 1.1 官方网站 2. Navidrome 简介2.1 简介2.2 特性 3. 准备工作4. 视频教程5. 界面演示5.1 初始化页5.2 专辑页 前言 之前给大家介绍过 Koel 音频流服务&#xff0c;就是为了解决大家的这个问题&#xff1a;下载下来的音乐&#xff0c;只能在本机欣赏&…

2023年“羊城杯”网络安全大赛 Web方向题解wp 全

团队名称&#xff1a;ZhangSan 序号&#xff1a;11 不得不说今年本科组打的是真激烈&#xff0c;初出茅庐的小后生没见过这场面QAQ~ D0n’t pl4y g4m3!!! 简单记录一下&#xff0c;实际做题踩坑很多&#xff0c;尝试很多。 先扫了个目录&#xff0c;扫出start.sh 内容如下…