Dubbo扩展点加载机制

        加载机制中已经存在的一些关键注解,如@SPI、©Adaptive> ©Activateo然后介绍整个加载机制中最核心的ExtensionLoader的工作流程及实现原理。最后介绍扩展中使用的类动态编译的实 
现原理。

Java SPI

Java 5 中的服务提供商icon-default.png?t=O83Ahttps://docs.oracle.com/javase/1.5.0/docs/guide/jar/jar.html#Service%20Provider

SPI 插件扩展点使用手册

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/spi/

JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现则初始化很耗时,如果没 
用上也加载,则浪费资源。

如果扩展加载失败,则连扩展的名称都蕤取不到了。比如JDK标准的ScriptEngine,通过 
getName ()获取脚本类型的名称,如果RubyScriptEngine因为所依赖的j ruby .jar不存在,导致 
RubyScriptEngine类加载失败,这个失败原因被“吃掉” 了,和Ruby对应不起来,当用户 
执行Ruby脚本时,会报不支持Ruby,而不是真正失败的原因。


增加了对扩展IoC和AOP的支持,一个扩展可以直接setter注入其他扩展。在Java SPI的使 
用示例章节(代码清单4-1 )中已经看到,java.util.ServiceLoader会一次把Printservice 
接口下的所有实现类全部初始化,用户直接调用即可oDubbo SPI只是加载配置文件中的类, 
并分成不同的种类缓存在内存中,而不会立即全部初始化,在性能上有更好的表现。

 ProtocolFilterWrapper 是 Dubbo 框架中的一个核心类,用于在服务提供者和消费者之间添加过滤器链。ProtocolFilterWrapper 通过 @Activate 注解来确定哪些过滤器适用于当前的 URL。以下是 ProtocolFilterWrapper 确定过滤器适用当前 URL 的详细过程:
1. ProtocolFilterWrapper 类
ProtocolFilterWrapper 是一个装饰器模式的实现,它包装了一个 Protocol 实例,并在其上添加了过滤器链。以下是 ProtocolFilterWrapper 的主要逻辑:

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.rpc.Exporter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Protocol;

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        this.protocol = protocol;
    }

    @Override
    public int getDefaultPort() {
        return protocol.getDefaultPort();
    }

    @Override
    public Exporter<T> export(Exporter<T> exporter) {
        return new InvokerDelegator<>(wrapInvoker(exporter.getInvoker()), exporter);
    }

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) {
        return wrapInvoker(protocol.refer(type, url));
    }

    private <T> Invoker<T> wrapInvoker(Invoker<T> invoker) {
        URL url = invoker.getUrl();
        // 获取所有激活的过滤器
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(url, Constants.KEYS, Constants.DEFAULT_KEY);
        if (filters.size() == 0) {
            return invoker;
        }
        return new FilterChainWrapper(invoker, filters);
    }

 SPI 提供商可以调用 ExtensionLoader.getActivateExtension(URL, String, String) 以查找具有给定条件的所有已激活的扩展。而getActivateExtension 会间接调用 com.alibaba.dubbo.common. extension.ExtensionLoader#loadExtensionClasses

其中 Type 是 由 ExtensionLoader.getExtensionLoader(Filter.class),决定为 Filter.

L565 - 567 就是解析 Filter 的接口上@SPI注解信息.(Filter.class 也可以替换成其他的类性

com.alibaba.dubbo.common.extension.ExtensionLoader#loadDirectory 会间接调用 com.alibaba.dubbo.common.extension.ExtensionLoader#loadClass

在此方法中会解析注解@Adaptive、@Activate

    /**
     * @param extensionClasses ExtensionLoader#cachedClasses 成员变量
     * @param resourceURL
     * @param clazz ExtensionLoader#loadResource 中 加载 Class.forName(  类全限定名 )
     * @param name  ExtensionLoader#loadResource 中 在配置文件中设置的别名
     *              上两参数请参考 com.alibaba.dubbo.common.extension.SPI
     * @throws NoSuchMethodException
     */
    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error when load extension class(interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + "is not subtype of interface.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            // 由于调用者 ExtensionLoader.loadResource 循环调用了 loadClass ,如果类上标注了 @Adaptive 注解,则该类为 Adaptive 类,并且只能有一个
            if (cachedAdaptiveClass == null) {
                cachedAdaptiveClass = clazz;
            } else if (!cachedAdaptiveClass.equals(clazz)) {
                throw new IllegalStateException("More than 1 adaptive class found: "
                        + cachedAdaptiveClass.getClass().getName()
                        + ", " + clazz.getClass().getName());
            }
        } else if (isWrapperClass(clazz)) {
            // 判断为 包装类,则维护到 ExtensionLoader.cachedWrapperClasses
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {
            clazz.getConstructor();
            if (name == null || name.length() == 0) {
                // 如果没有名字则尝试扫描 @Extension 注解, Extension 注解已经弃用了
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
            // 将首个类上@Activate 信息维护到 ExtensionLoader.cachedActivates 中
            // 将 别名 维护到 ExtensionLoader.cachedNames 中
            // 将 别名&类 维护到 ExtensionLoader#cachedClasses 中
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                Activate activate = clazz.getAnnotation(Activate.class);
                if (activate != null) {
                    cachedActivates.put(names[0], activate);
                }
                for (String n : names) {
                    if (!cachedNames.containsKey(clazz)) {
                        cachedNames.put(clazz, n);
                    }
                    Class<?> c = extensionClasses.get(n);
                    if (c == null) {
                        extensionClasses.put(n, clazz);
                    } else if (c != clazz) {
                        throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                    }
                }
            }
        }
    }

工作流程

  1. 框架读取SPI对应路径下的配置文件,并根据配置加载所有扩展类并缓存(不初始化)。
  2. 根据传入的名称初始化对应的扩展类。
  3. 尝试查找符合条件的包装类:包含扩展点的setter方法,
  4. 返回对应的扩展类实例。

        getAdaptiveExtension也相对独立,只有加载配置信息部分与getExtension共用了同一个 
方法。和获取普通扩展类一样,框架会先检查缓存中是否有已经初始化化好的Adaptive实例, 
没有则调用createAdaptiveExtension重新初始化。初始化过程分为4步:
和getExtension 一样先加载配置文件。

  1. 生成自适应类的代码字符串。
  2.  获取类加载器和编译器,并用编译器编译刚才生成的代码字符串。Dubbo 一共有三种 
  3. 类型的编译器实现。
  4. 返回对应的自适应类实例。

getExtension 的实现原理

ExtensionLoader#getExtension 会调用ExtensionLoader#createExtension 方法

    /**
     *  创建扩展实例
     * @param name  别名
     * @return
     */
    private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            // 先尝试从缓存中获取实例
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                // 不存在的话则通过反射创建实例
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // 反射执行 setter 方法
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                // 检查是否有包装类
                for (Class<?> wrapperClass : wrapperClasses) {
                    // 通过反射创建包装类实例,再执行包装实例的 setter 方法, 最后更新包装类实例
                    // 这里我们能看出 包装类需要 实现 接口,并且包装类需要有一个构造函数,构造参数是接口类型
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }

getAdaptiveExtension 的实现原理

        在getAdaptiveExtension()方法中,会为扩展点接口自动生成实现类字符串,实现类主要包含以下逻辑:为接口中每个有^Adaptive注解的方法生成默实现(没有注解的方法则生成空实现),每个默认实现都会从URL中提取Adaptive参数值,并以此为依据动态加载扩展点。然后,框架会使用不同的编译器,把实现类字符串编译为自适应类并返回。

         生成完代码之后就要对代码进行编译,生成一个新的Classo Dubbo中的编译器也是一个自 
适应接口,但@Adaptive注解是加在实现类AdaptiveCompiler上的。这样一来AdaptiveCompiler 
就会作为该自适应类的默认实现,不需要再做代码生成和编译就可以使用了。

    private Class<?> createAdaptiveExtensionClass() {
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

// TODO :待续

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

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

相关文章

Elasticsearch向量检索需要的数据集以及768维向量生成

Elasticsearch8.17.0在mac上的安装 Kibana8.17.0在mac上的安装 Elasticsearch检索方案之一&#xff1a;使用fromsize实现分页 快速掌握Elasticsearch检索之二&#xff1a;滚动查询(scrool)获取全量数据(golang) Elasticsearch检索之三&#xff1a;官方推荐方案search_after…

网关的主要作用

在网络安全领域&#xff0c;网关扮演着举足轻重的角色&#xff0c;它不仅是网络间的桥梁&#xff0c;更是安全防线的守护者。以下是网关在网络安全中的几个关键作用&#xff1a; 1. 防火墙功能&#xff1a;网关常常集成了防火墙技术&#xff0c;能够对进出网络的数据包进行严格…

【Cocos TypeScript 零基础 4.1】

目录 背景滚动 背景滚动 创建一个 空节点 背景丟进去 ( 复制一个,再丢一次都行) 新建TS脚本 并绑定到 空节点 上 再对TS脚本进行编辑 export class TS2bg extends Component {property (Node) // 通过属性面板去赋值bg1:Node nullproperty (Node) bg2:Node nullprope…

如何利用群晖NAS实现远程访问你的网页版Linux虚拟桌面环境

文章目录 前言1. 下载Docker-Webtop镜像2. 运行Docker-Webtop镜像3. 本地访问网页版Linux系统4. 群晖NAS安装Cpolar工具5. 配置异地访问Linux系统6. 异地远程访问Linux系统7. 固定异地访问的公网地址 前言 今天我要给大家介绍一下如何在群晖NAS设备上部署Docker-Webtop&#x…

MySQL 04 章——运算符

一、算数运算符 算数运算符主要用于数学运算&#xff0c;其可以连接运算符前后的两个数值或表达式 运算符名称作用示例加法运算符计算两个值或表达式的和SELECT AB-减法运算符计算两个值或表达式的差SELECT A-B*乘法运算符计算两个值或表达式的乘积SELECT A*B/或DIV除法运算符…

ES IK分词器插件

前言 ES中默认了许多分词器&#xff0c;但是对中文的支持并不友好,IK分词器是一个专门为中文文本设计的分词工具&#xff0c;它不是ES的内置组件&#xff0c;而是一个需要单独安装和配置的插件。 Ik分词器的下载安装&#xff08;Winows 版本&#xff09; 下载地址&#xff1a;…

Oracle DG备库数据文件损坏修复方法(ORA-01578/ORA-01110)

今天负责报表的同事反馈在DG库查询时出现如下报错 ORA-01578:ORACLE数据块损坏(文件号6,块号 2494856)ORA-01110:数据文件6: /oradata/PMSDG/o1 mf users_molczgmn_.dbfORA-26040:数据块是使用 NOLOGGING 选项加载的 可以看到报错是数据文件损坏&#xff0c;提示了file id和b…

idea无法安装插件

目录 修改工具配置 本地安装 无法下载很多时候就是延迟太高导致的&#xff0c;我们先打开插件官网看一下 Python - IntelliJ IDEs Plugin | Marketplace 修改工具配置 1、配置代理&#xff08;点击 setting-点击 plugins-在点击 http proxy Settings&#xff09; 输入&…

Linux部署web项目【保姆级别详解,Ubuntu,mysql8.0,tomcat9,jdk8 附有图文】

文章目录 部署项目一.安装jdk1.1 官网下载jdk81.2 上传到Linux1.3 解压1.4 配置环境变量1.5 查看是jdk是否安装成功 二.安装TomCat2.1 官网下载2.2 上传到Linux2.3 解压2.4配置2.5 启动Tomcat2.6 验证是否成功 三.安装mysql四.部署javaweb项目4.1 打包4.2 启动tomcat 部署项目 …

服务器等保测评日志策略配置

操作系统日志 /var/log/message 系统启动后的信息和错误日志&#xff0c;是Red Hat Linux中最常用的日志之一 /var/log/secure 与安全相关的日志信息 /var/log/maillog 与邮件相关的日志信息 /var/log/cron 与定时任务相关的日志信息 /var/log/spooler 与UUCP和news设备相关的…

初学stm32 --- FSMC驱动LCD屏

目录 FSMC简介 FSMC框图介绍 FSMC通信引脚介绍 FSMC_NWE 的作用 FSMC_NWE 的时序关系 FSMC_NOE 的含义 FSMC_NOE 的典型用途 FSMC_NOE 的时序关系 使用FSMC驱动LCD FSMC时序介绍 时序特性中的 OE ILI9341重点时序&#xff1a; FSMC地址映射 HADDR与FSMC_A关系 LCD的…

业务模型与UI设计

业务数据模型的设计、UI设计这应该是程序设计中不可缺少的部分。做程序设计的前提应该先把这两块设计好&#xff0c;那么&#xff0c;来一个实际案例&#xff0c;看看这2块的内容。 汽车保养记录业务模型与UI设计&#xff1a; 一、【车辆清单】 记录车辆相关的数据&#xff0…

【JavaScript】变量-常量-数据类型-类型转换

目录 一、JavaScript 介绍 1. JavaScript &#xff08;是什么&#xff1f;&#xff09; 2. 作用&#xff08;做什么&#xff1f;&#xff09; 3. JavaScript的组成&#xff08;有什么&#xff1f;&#xff09; 3.1 ECMAScript: 3.2 Web APIs : 总结&#xff1a; 4. Jav…

day30-awk精讲

awk其实不仅仅是工具软件&#xff0c;还是一种编程语言。 不过&#xff0c;本文只介绍它的命令行用法&#xff0c;对于大多数场合&#xff0c;应该足够用了。 awk是什么 awk是一个强大的linux命令&#xff0c;有强大的文本格式化的能力&#xff0c;好比将一些文本数据格式化…

实战设计模式之建造者模式

概述 在实际项目中&#xff0c;我们有时会遇到需要创建复杂对象的情况。这些对象可能包含多个组件或属性&#xff0c;而且每个组件都有自己的配置选项。如果直接使用构造函数或前面介绍的工厂方法来创建这样的对象&#xff0c;可能会导致以下两个严重问题。 1、参数过多。当一个…

滤波器的主要参数

为什么选择高阶&#xff1a; 滤波器的主要参数通常包括以下几个方面&#xff1a; 截止频率 (Cutoff Frequency)&#xff1a; 这是滤波器能够有效通过或抑制信号的频率点。对于低通滤波器&#xff0c;信号低于截止频率的部分会被通过&#xff0c;高于截止频率的部分会被衰减。高…

HNSW概述

1. \textbf{1. } 1. 一些导论 1.1. \textbf{1.1. } 1.1. 朴素基于图的 ANN \textbf{ANN} ANN 1️⃣建图&#xff1a;对数据库中所有的点&#xff0c;构建 k -NN k\text{-NN} k-NN图(下图以 3 -NN 3\text{-NN} 3-NN为例) 2️⃣检索&#xff1a; GreedySearch \text{GreedySearch…

小程序学习07—— uniapp组件通信props和$emit和插槽语法

目录 一 父组件向子组件传递消息 1.1 props &#xff08;a&#xff09;传递静态或动态的 Prop &#xff08;b&#xff09;单向数据流 二 子组件通知父组件 2.1 $emit &#xff08;a&#xff09;定义自定义事件 &#xff08;b&#xff09;绑定自定义事件 三 插槽语法…

【视频笔记】基于PyTorch从零构建多模态(视觉)大模型 by Umar Jamil【持续更新】

视频链接: 基于PyTorch从零构建多模态(视觉)大模型 by Umar Jamil 从头编写一个视觉语言模型:PloyGamma,是谷歌的一个模型 1:原始图像 2:视觉编码器(本文是viT),通过对比学习进行训练。这个对比学习最开始是CLIP,后来被谷歌改成了SigLIP 3:线性投影层 4:如何将图…

UniApp | 从入门到精通:开启全平台开发的大门

UniApp | 从入门到精通:开启全平台开发的大门 一、前言二、Uniapp 基础入门2.1 什么是 Uniapp2.2 开发环境搭建三、Uniapp 核心语法与组件3.1 模板语法3.2 组件使用四、页面路由与导航4.1 路由配置4.2 导航方法五、数据请求与处理5.1 发起请求5.2 数据缓存六、样式与布局6.1 样…