首页 > 其他分享 >dubbo spi 的应用指南

dubbo spi 的应用指南

时间:2024-08-30 18:52:34浏览次数:15  
标签:指南 dubbo execute url class spi Adaptive public

文章目录


前言

SPI全称为Service Provider Interface,是一种服务发现机制。在Dubbo中,SPI机制被广泛应用于加载和扩展各种组件,如协议(Protocol)、负载均衡(LoadBalance)、注册中心(Registry)等。Dubbo并未直接使用Java原生的SPI机制,而是对其进行了增强,以满足更复杂和灵活的需求。


Dubbo 加载扩展的整个流程如下:
在这里插入图片描述

一、配置文件目录

  • META-INF/services/:
    用途:这个目录下的SPI配置文件主要用于兼容JDK原生的SPI机制。

  • META-INF/dubbo/:
    用途:这个目录专门用于存放用户自定义的Dubbo SPI配置文件,开发应用的存放目录。格式为文件名为全路径名了,内容采用键值对(Key-Value,简称KV)的格式,其中key被称为扩展名(ExtensionName),value则是对应的具体实现类。
    例如
    目录:META-INF/dubbo/org.apache.dubbo.rpc.Protocol
    内容:http=com.sjl.HttpProtocol

  • META-INF/dubbo/internal/:
    用途:这个目录用于存放Dubbo内部使用的SPI配置文件

二、标准SPI

// "one" 是默认拓展实现类
@SPI("one")
public interface BaseSpi {

    void execute();

}

spi实现

public class OneBaseSpi implements BaseSpi{

    @Override
    public void execute() {
        System.out.println("one base spi");
    }

}
public class TwoBaseSpi implements BaseSpi{

    @Override
    public void execute() {
        System.out.println("two base spi");
    }
    
}

配置
在这里插入图片描述

one=org.sjl.spi.base.OneBaseSpi
two=org.sjl.spi.base.TwoBaseSpi

测试

@Component
public class BaseTest implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args){
        ExtensionLoader<BaseSpi> extensionLoader = ExtensionLoader.getExtensionLoader(BaseSpi.class);
        // 获取key为 one 的拓展实现类
        BaseSpi one = extensionLoader.getExtension("one");
        one.execute();
        // 获取key为 two 的拓展实现类
        BaseSpi two = extensionLoader.getExtension("two");
        two.execute();
        // 获取 spi 注解上的默认拓展实现类
        BaseSpi def = extensionLoader.getDefaultExtension();
        def.execute();
    }

}

测试结果
在这里插入图片描述

三、自适应SPI(Adaptive SPI)

1、@Adaptive说明

1.1、方法级别的注解:

  • @Adaptive 注解标记在接口方法上时,Dubbo 会在初始化扩展点时自动生成一个动态代理类,这个代理类会包含被 @Adaptive 注解修饰的方法的代理实现。这些代理实现会根据运行时传入的参数(URL中的参数)来动态选择并调用合适的实现类。URL可以直接作为入参,或者作为入参的入参如org.apache.dubbo.rpc.Invoker
  • @Adaptive 注解中的 String[] value()
    • 没有填写时,将接口名转换为小写并使用点(.)分隔,然后从 URL 中查找对应的参数值,例如org.sjl.spi.Adaptive.AdaptiveSpi,则对应的key为adaptive.spi
    • 填写时,则通过value的值作为key
      • value数组等于1
        例如@Adaptive(“ADP”)
        则从url的取值为
        url.getParameter(“ADP”);
      • value数组大于1
        例如@Adaptive(“ADP”,“DDD”)
        则从url的取值为
        url.getParameter(“ADP”, url.getParameter(“DDD”));

1.2、 类级别的注解:

  • @Adaptive 注解标记在类上时,该类通常会被直接作为自适应实现类来进行代理。

2、@Adaptive作用在类上

spi接口

@SPI("one")
public interface AdaptiveClassSpi {

    void execute(String name);

}

spi实现类

public class OneAdaptiveClassSpi implements AdaptiveClassSpi{

    public void execute(String name){
        System.out.println("One Adaptive Class Spi");
    }

}
public class TwoAdaptiveClassSpi implements AdaptiveClassSpi{

    public void execute(String name){
        System.out.println("Two Adaptive Class Spi");
    }

}

自定义代理类

@Adaptive
public class AdaptiveClassSpiProxy implements AdaptiveClassSpi {

    private static final ExtensionLoader<AdaptiveClassSpi> extensionLoader = ExtensionLoader.getExtensionLoader(AdaptiveClassSpi.class);

    public void execute(String name) {
        AdaptiveClassSpi adaptiveClassSpi = null;
        if (StringUtils.isBlank(name) || !extensionLoader.hasExtension(name)) {
            adaptiveClassSpi = extensionLoader.getDefaultExtension();
        } else {
            adaptiveClassSpi = extensionLoader.getExtension(name);
        }
        adaptiveClassSpi.execute(name);
    }

}

配置

one=org.sjl.spi.Adaptive.OneAdaptiveClassSpi
two=org.sjl.spi.Adaptive.TwoAdaptiveClassSpi
adaptive=org.sjl.spi.Adaptive.AdaptiveClassSpiProxy

测试

@Component
public class AdaptiveClassSpiTest implements ApplicationRunner {

    private static final ExtensionLoader<AdaptiveClassSpi> adaptiveClassSpi = ExtensionLoader.getExtensionLoader(AdaptiveClassSpi.class);

    @Override
    public void run(ApplicationArguments args) throws Exception {
        AdaptiveClassSpi adaptiveExtension = adaptiveClassSpi.getAdaptiveExtension();
        adaptiveExtension.execute("one");
        adaptiveExtension.execute("two");
    }

}

结果
在这里插入图片描述

3、@Adaptive作用在方法上

spi接口

@SPI("one")
public interface AdaptiveSpi {

    public static final String ADP = "ADP";

    @Adaptive(ADP)
    void execute(URL url);

}

spi实现类

public class OneAdaptiveSpi implements AdaptiveSpi{

    public void execute(URL url){
        System.out.println("One Adaptive Spi");
    }

}
public class TwoAdaptiveSpi implements AdaptiveSpi{

    public void execute(URL url){
        System.out.println("Two Adaptive Spi");
    }

}

配置

one=org.sjl.spi.Adaptive.OneAdaptiveSpi
two=org.sjl.spi.Adaptive.TwoAdaptiveSpi

测试

@Component
public class AdaptiveSpiTest implements ApplicationRunner {

    private static final ExtensionLoader<AdaptiveSpi> extensionLoader = ExtensionLoader.getExtensionLoader(AdaptiveSpi.class);

    @Override
    public void run(ApplicationArguments args) throws Exception {
    	// 获取自适应代理类 AdaptiveSpi$Adaptive
        AdaptiveSpi adaptiveExtension = extensionLoader.getAdaptiveExtension();
        URL url = buildURL();
        // 没有对应ADP参数,则取默认spi
        adaptiveExtension.execute(url);

        URL url1 = buildURL().addParameter(AdaptiveSpi.ADP, "two");
        // 取key为ADP的参数的spi
        adaptiveExtension.execute(url1);
    }

    public static URL buildURL() {
        URL url = new URL(null, "127.0.0.1", 8090);
        return url;
    }
}

生成的代理类

public class AdaptiveSpi$Adaptive implements org.sjl.spi.Adaptive.AdaptiveSpi {
    public void execute(org.apache.dubbo.common.URL arg0) {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("ADP", "one");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.sjl.spi.Adaptive.AdaptiveSpi) name from url (" + url.toString() + ") use keys([ADP])");
        ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.sjl.spi.Adaptive.AdaptiveSpi.class);
        org.sjl.spi.Adaptive.AdaptiveSpi extension = (org.sjl.spi.Adaptive.AdaptiveSpi) scopeModel.getExtensionLoader(org.sjl.spi.Adaptive.AdaptiveSpi.class).getExtension(extName);
        extension.execute(arg0);
    }
}

结果
在这里插入图片描述

三、激活SPI(Activate SPI)

Dubbo 的激活点机制基于 @Activate 注解完成,可以用于实现根据条件加载多个 SPI 激活点实现类。

  • @Activate 注解:
    • String[] group() default {}:
      • getActivateExtension 接口传入的 group 参数为 null 或者 length==0,表示不限制 group;
      • @Activate 注解中的参数 groups 是否包含传入的限制参数 group,如果包含,则允许加载当前的 SPI 实现。
    • String[] value() default {}:
      • @Activate 注解没有 value() 属性,则默认所有值不包括空;
      • URL#getParameters() 中的一个参数名,则认为默认是允许当前的 SPI 实现加载的
    • int order() default 0;
      • 表示加载顺序,数字越小越靠前
        spi接口
@SPI
public interface ActivateSpi {

    void execute();

}

spi实现

@Activate(value = "one",group = {"active","active2"}, order = 1)
public class OneActivateSpi implements ActivateSpi{

    @Override
    public void execute() {
        System.out.println("One Activate Spi");
    }
}

@Activate(value = "two",group = "active", order = 2)
public class TwoActivateSpi implements ActivateSpi{

    @Override
    public void execute() {
        System.out.println("Two Activate Spi");
    }
}

@Activate(group = "active", order = 3)
public class ThreeActivateSpi implements ActivateSpi{

    @Override
    public void execute() {
        System.out.println("Three Activate Spi");
    }
}

@Activate(order = 4, value = "four")
public class FourActivateSpi implements ActivateSpi{

    @Override
    public void execute() {
        System.out.println("Four Activate Spi");
    }
}

配置

one=org.sjl.spi.Activate.OneActivateSpi
two=org.sjl.spi.Activate.TwoActivateSpi
three=org.sjl.spi.Activate.ThreeActivateSpi
four=org.sjl.spi.Activate.FourActivateSpi

测试

@Component
public class ActiveSpiTest implements ApplicationRunner {

    private static final ExtensionLoader<ActivateSpi> extensionLoader = ExtensionLoader.getExtensionLoader(ActivateSpi.class);

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("仅指定 group");
        String group = "active";
        // 只会获取到仅设置了group为active 且 value值不填写的
        List<ActivateSpi> activateSpiList = extensionLoader.getActivateExtension(buildURL(), new String[]{}, group);
        activateSpiList.forEach(ActivateSpi::execute);

        System.out.println("指定 group 和 value");
         // 获取到仅设置了group为active 且 value值不填写的或者value为one的
        List<ActivateSpi> activateSpiList1 = extensionLoader.getActivateExtension(buildURL(), new String[]{"one"}, group);
        activateSpiList1.forEach(ActivateSpi::execute);

        System.out.println("指定 value");
        // 获取到设置了group的所有值或者不设置  且 value需要等于four或者不填的
        List<ActivateSpi> activateSpiList2 = extensionLoader.getActivateExtension(buildURL(), new String[]{"four"}, null);
        activateSpiList2.forEach(ActivateSpi::execute);

    }

    public static URL buildURL() {
        URL url = new URL(null, "127.0.0.1", 8090);
        return url;
    }
}

测试结果
在这里插入图片描述

四、Dubbo AOP

AOP 增强,通常以Wrapper结尾,且需要实现如下协议

  • wrapper 类也必须实现 SPI 接口
  • wrapper 类必须有一个含有单个 SPI 参数的构造器

针对上面的 AdaptiveSpi ,我们增加AOP强化功能
增强类

public class AdaptiveSpiWrapper implements AdaptiveSpi {

    private AdaptiveSpi adaptiveSpi;

    public AdaptiveSpiWrapper(AdaptiveSpi adaptiveSpi){
        this.adaptiveSpi = adaptiveSpi;
    }

    @Override
    public void execute(URL url) {
        System.out.println("@AOP(order = 100)");
        adaptiveSpi.execute(url);
    }

}
public class AdaptiveSpiWrapper2 implements AdaptiveSpi {

    private AdaptiveSpi adaptiveSpi;

    public AdaptiveSpiWrapper2(AdaptiveSpi adaptiveSpi){
        this.adaptiveSpi = adaptiveSpi;
    }

    @Override
    public void execute(URL url) {
        System.out.println("@AOP(order = -100)");
        adaptiveSpi.execute(url);
    }

}

配置

  • 配置的先后顺序,代表AOP的先后顺序
one=org.sjl.spi.Adaptive.OneAdaptiveSpi
two=org.sjl.spi.Adaptive.TwoAdaptiveSpi
wrapper2=org.sjl.spi.aop.AdaptiveSpiWrapper2
wrapper=org.sjl.spi.aop.AdaptiveSpiWrapper

测试

@Component
public class AdaptiveSpiTest implements ApplicationRunner {

    private static final ExtensionLoader<AdaptiveSpi> extensionLoader = ExtensionLoader.getExtensionLoader(AdaptiveSpi.class);

    @Override
    public void run(ApplicationArguments args) throws Exception {
        AdaptiveSpi adaptiveExtension = extensionLoader.getAdaptiveExtension();
        URL url = buildURL();
        // 此时的链路为 AdaptiveSpiWrapper2 -> AdaptiveSpiWrapper -> OneAdaptiveSpi
        adaptiveExtension.execute(url);

        URL url1 = buildURL().addParameter(AdaptiveSpi.ADP, "two");
        // 此时的链路为 AdaptiveSpiWrapper2 -> AdaptiveSpiWrapper -> TwoAdaptiveSpi
        adaptiveExtension.execute(url1);
    }

    public static URL buildURL() {
        URL url = new URL(null, "127.0.0.1", 8090);
        return url;
    }
}

结果
在这里插入图片描述

五、Dubbo IOC

Dubbo IOC 的实现机制

  • setter 方法注入依赖。
  • 注入为spring bean 或者 自适应扩展点

针对上面的代码,在OneBaseSpi 依赖注入 AdaptiveSpi

public class OneBaseSpi implements BaseSpi{

    private AdaptiveSpi adaptiveSpi;

    public void setAdaptiveSpi(AdaptiveSpi adaptiveSpi) {
        this.adaptiveSpi = adaptiveSpi;
    }

    @Override
    public void execute() {
        adaptiveSpi.execute(buildURL());
        System.out.println("one base spi");
    }

    public static URL buildURL() {
        URL url = new URL(null, "127.0.0.1", 8090);
        return url;
    }

}

配置保存不变

one=org.sjl.spi.base.OneBaseSpi

测试

@Component
public class BaseTest implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args){
        ExtensionLoader<BaseSpi> extensionLoader = ExtensionLoader.getExtensionLoader(BaseSpi.class);
        BaseSpi one = extensionLoader.getExtension("one");
        one.execute();
    }

}

结果
在这里插入图片描述

标签:指南,dubbo,execute,url,class,spi,Adaptive,public
From: https://blog.csdn.net/weixin_44550507/article/details/141691158

相关文章

  • LTspice使用教程,LTspice仿真教程资源大全
      LTspice简介 LTspice是英文SimulationProgramwithIntegratedCircuitEmphasis的缩写,意思是集成电路通用模拟程序。是ADI旗下的一款免费软件,很多国外的工程师、教授、学生基本用的都是LTspice,感觉应该是最好用的一款,也是教程在国内普及的比较好的一款仿真软件......
  • HarmonyOS开发指南:ArkUI自定义Toast弹窗样式规范
     可以通过使用自定义弹窗和定时器达到类似Toast的效果。场景一:自定义弹窗实现弹窗中加入icon和文字,支持Button。方案:⦁   使用@CustomDialog装饰器装饰自定义弹窗,在此装饰器内进行自定义内容(也就是弹框内容)、并创建构造器,与装饰器呼应相连。⦁   使用定时器,在页面......
  • VS Code 代码片段指南: 从基础到高级技巧
    前言“系列首发于公众号『非同质前端札记』,若不想错过更多精彩内容,请“星标”一下,敬请关注公众号最新消息。今天咱们来聊聊VSCode里的自定义代码片段。这玩意儿简直是提升编码效率的神器,用好了能让你敲代码更方便!不管你是刚入行的菜鸟还是身经百战的老兵,这篇攻略都......
  • VS Code 代码片段指南: 从基础到高级技巧
    前言“系列首发于公众号『非同质前端札记』,若不想错过更多精彩内容,请“星标”一下,敬请关注公众号最新消息。今天咱们来聊聊VSCode里的自定义代码片段。这玩意儿简直是提升编码效率的神器,用好了能让你敲代码更方便!不管你是刚入行的菜鸟还是身经百战的老兵,这篇攻略都......
  • 数据安全指南:电脑重要文件如何加密?
    在工作中,电脑是重要的办公工具,可以处理和保存大量数据。为了避免重要文件泄露,我们需要加密保护电脑重要文件。下面我们就来了解一下电脑重要文件的加密方法。EFS加密EFS是Windows系统提供的数据加密功能,基于公钥加密策略,采用透明加密方法。在加密文件时,不需要设置密码,通过......
  • Java ORM 框架指南
    Java应用程序通常需要与关系型数据库交互来存储和检索数据。编写这些SQL查询不仅繁琐,还容易出错,影响开发速度和代码的可维护性。这个时候,ORM(对象关系映射)框架就显得特别重要。ORM框架能弥合Java面向对象编程和数据库之间的差距。它们允许我们使用Java对象来处理数据库中的数据,......
  • GDB调试器使用指南:设置断点、单步调试和查看寄存器状态
    使用​ddd./eg​设置​​断点​​运行​​点击​run​​单步调试​Next​命令将执行到下一条指令。这包括在必要时执行整个函数。步骤命令将执行一步,如有必要,单步执行函数。对于单个非功能指令,Next​命令和Step​命令之间没有区别查看寄存器状态​Status→Registe......
  • 日常避坑指南:重试装饰器的正确使用方式
    在日常开发中,重试机制是提高代码健壮性的重要手段之一,尤其是在处理网络请求时,遇到超时或临时性错误的情况并不少见。通过重试,我们可以在一定程度上降低这些临时问题带来的影响。然而,如果使用不当,重试机制本身也可能引发新的问题,甚至让问题更加难以排查。问题背景在一次项目......