首页 > 其他分享 >Dubbo的spi机制分析和实战案例

Dubbo的spi机制分析和实战案例

时间:2023-06-11 22:32:23浏览次数:40  
标签:实战 Dubbo SPI PersonService class tian spi public


留下来一个问题,想深入学习Dubbo源码,你需要具备哪些技术点。

技术点

  • Spring xml自定义标签 或 通过@DubboComponentScan("con.tian.dubbo.service")扫描@DubboService注解
  • 设计模式:模板方法模式、装饰器模式、责任链模式、代理模式、工厂模式
  • Netty基本知识:创建服务端和客户端,handler,编解码,序列化
  • Dubbo SPI 机制

之前,已经聊过模板方法模式,本文咱们来聊聊Dubbo中的SPI机制,其他相关的我会逐个分享出来的。

为什么把SPI放在前面,主要是这个Dubbo的SPI机制实在是太重要了。

什么是SPI

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的,它可以用来启用框架扩展和替换组件。

当服务的提供者(provider),提供了一个接口多种实现时,  一般会在jar包的META-INF/services/目录下,创建该接口的同名文件。 该文件里面的内容就是该服务接口的具体实现类的名称。而当外部加载这个模块的时候,  就能通过该jar包META-INF/services/里的配置文件得到具体的实现类名,并加载实例化,完成模块的装配。

如果对JDK自带的SPI机制还不是很熟悉的,请先去熟悉一下,本文就不再赘述了。因为你看到文章标题,就应该有点SPI的基础。

Dubbo SPI 入门

我们平时使用手机支付时,通常都会选择支付宝支付或者微信支付。

我们用代码演示:

@SPI("wechat")
public interface Pay {
    String way();
}
public class AliPay implements Pay {
    @Override
    public String way() {
        System.out.println("我正在使用 支付宝 支付");
        return "Alipay";
    }
}
public class WechatPay implements Pay {
    @Override
    public String way() {
        System.out.println("我正在使用 微信 支付");
        return "wechat";
    }
}

测试类:

public class PayDemo {
    public static void main(String[] args) {
        // 获取到用于加载Order类型扩展类实例的extensionLoader实例
        ExtensionLoader<Pay> loader = ExtensionLoader.getExtensionLoader(Pay.class);
        //如果loader.getDefaultExtension()返回的是
        //@SPI("wechat")注解中默认值wechat对应的WechatPay
        Pay alipay = loader.getExtension("alipay");
        System.out.println(alipay.way());
    }
}

输出:

我正在使用 支付宝 支付
Alipay

实现步骤

1、定义一个接口,然后在接口上加上 @SPI注解

2、写好实现类

3、创建好META-INFO/dubbo文件夹,并在该目录下创建好一个文件,文件名=接口全路径名称

4、把我们的实现类配置在上面的文件里,以key=value形式。key自定义名称,value就是我们对应实现类名全路径名称。

5、通过ExtensionLoader<Pay> loader = ExtensionLoader.getExtensionLoader(Pay.class);加载配置文件

6、通过指定名称loader.getExtension("alipay");获取对应实现类的实例(其实是经过多层包装的实现类,后面再细说)

7、调用实现类的方法

关于配置文件

dubbo在3.0版本之前,我们的配置文件只能在下面三个路径下:

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

并且是按照上面顺序来加载。

dubbo3.0+版本后,我们就可以自定义类配置文件目录了。

自定义扩展点配置文件目录

想自定义目录,需要实现接口:org.apache.dubbo.common.extension.LoadingStrategy

我们通过类关系图,可以看到dubbo有三个实现类,从名字就看出和上面的三个META-INF下的三个目录名称一样。

Dubbo的spi机制分析和实战案例_接口

下面,我们来自定义实现类:com.tian.spi.TianLoadingStrategy

public class TianLoadingStrategy implements LoadingStrategy {
    @Override
    public String directory() {
        //我们自定义目录
        return "META-INF/tian/";
    }
    @Override
    public boolean overridden() {
        return true;
    }

    @Override
    public int getPriority() {
        return 100;
    }

    @Override
    public String getName() {
        return "TIAN";
    }
}

上面的三个路径中,META-INF/services目录是用来兼容JavaSPI的,所以我们需要这么干。

在自己的项目中,META-INF/services目录下见一个文件:

org.apache.dubbo.common.extension.LoadingStrategy

然后把我们的实现类全路径名称放进去(这里用的是Java的SPI机制)。

Dubbo的spi机制分析和实战案例_大数据_02

我们在resources目录下建一个目录:META-INF/tian

Dubbo的spi机制分析和实战案例_接口_03

com.tian.spi.Pay内容:

wechat=com.tian.spi.WechatPay
alipay=com.tian.spi.AliPay

运行项目:

Dubbo的spi机制分析和实战案例_大数据_04

我们再切换成wechat,运行结果:

Dubbo的spi机制分析和实战案例_spring boot_05

到此,我们自定义配置文件目录已经搞定。

尽管我们很少这么用,但是咱们不能说不知道,万一有天面试官抽风问你这个问题,你也能回答上来噻。

面试官问:dubbo3.0版本加入的新功能,你知道哪些?这里不就是新功能了吗?

自适应扩展点

在运行期间,根据上下文来决定当前返回哪个扩展点。

关键注解:@Adaptive

  • 如果修饰在类级别,那么直接返回修饰的类
  • 如果修饰在方法界别,动态创建一个代理类(javassist)
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
@Adaptive 在类上

我们继续使用上面的案例进行演示,新增一个类:AdaptivePay

@Adaptive
public class AdaptivePay implements Pay {
    private String defaultName;

    // 指定要加载扩展类的名称
    public void setDefaultName(String defaultName) {
        this.defaultName = defaultName;
    }

    @Override
    public String way() {
        System.out.println("======进入 AdaptivePay way方法=====");
        ExtensionLoader<Pay> loader = ExtensionLoader.getExtensionLoader(Pay.class);
        Pay pay;
        if (StringUtils.isEmpty(defaultName)) {
            // 加载SPI默认名称的扩展类
            pay = loader.getDefaultExtension();
        } else {
            // 加载指定名称的扩展类
            pay = loader.getExtension(defaultName);
        }
        return pay.way();
    }
}

测试类:

public class PayDemo {
    public static void main(String[] args) {
        Pay pay = ExtensionLoader.getExtensionLoader(Pay.class).getAdaptiveExtension(); 
        System.out.println(pay.way());
    }
}

输出结果:

======进入 AdaptivePay way方法=====
我正在使用 微信 支付
wechat

这里,证明了返回的就是加有注解@Adaptive的实现类。

@Adaptive 修饰方法

下面来看案例。

//默认是guangdong
@SPI("guangdong")
public interface PersonService {
    @Adaptive
    String queryCountry(URL url);
}
//实现类1
public class BeijingPersonServiceImpl implements PersonService {
    @Override
    public String queryCountry(URL url) {
        System.out.println("北京人");
        return "北京人";
    }
}
//实现类2
public class GuangdongPersonServiceImpl implements PersonService {
    @Override
    public String queryCountry(URL url) {
        System.out.println("广东人");
        return "广东人";
    }
}

META-INF/dubbo目录下新建文件:

com.tian.spi.PersonService

内容:

guangdong=com.tian.spi.GuangdongPersonServiceImpl
beijing=com.tian.spi.BeijingPersonServiceImpl

测试类:

public class PersonTest {
    public static void main(String[] args) {
        URL  url = URL.valueOf("dubbo://192.168.0.101:20880?person.service=guangdong");
        PersonService service = ExtensionLoader.getExtensionLoader(PersonService.class)
                .getAdaptiveExtension();
        service.queryCountry(url);
    }
}

运行结果:广东人

这个过程中会生成一个动态类,比如上面这个案例就会生成一个PersonService$Adaptive类,内容如下:

import org.apache.dubbo.common.extension.ExtensionLoader; 
public class PersonService$Adaptive implements com.tian.spi.PersonService {
    public java.lang.String queryCountry(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("person.service", "guangdong");
        
        if(extName == null) throw new IllegalStateException("Failed to get extension (com.tian.spi.PersonService) name from url (" + url.toString() + ") use keys([person.service])");
        
        com.tian.spi.PersonService extension = (com.tian.spi.PersonService)ExtensionLoader.getExtensionLoader(com.tian.spi.PersonService.class).getExtension(extName);
        return extension.queryCountry(arg0);
    }
}

从类的定义可以看出:PersonService$Adaptivecom.tian.spi.PersonService的子类。

上面这个类的内容,可以debug模式到ExtensionLoader中的createAdaptiveExtensionClass()方法里。

Xxx$Adaptive简要说明

PersonService$AdaptivequeryCountry()方法主要内容:

1、获取扩展名称

//通过"person.service"去URL中找,如果没有就用默认值guangdong。
String extName = url.getParameter("person.service", "guangdong");

这里的getParameter("person.service", "guangdong");中的参数person.service,这个一定要搞清楚是怎么来的,否则,看Dubbo源码基本上都会晕车。

注意:

如果 @Adaptive没有指定默认值,那么此时这个参数就是一个当前接口名称转换来的,比如:PersonService转换来就是person.service。再比如当前接口是Protocol,那么此时参数名称就是protocol

如果@Adaptive("xxx")给了默认值是xxx,那么此时的参数就是xxx

2、通过扩展名称获取具体实现实例

com.tian.spi.PersonService extension = (com.tian.spi.PersonService)ExtensionLoader.getExtensionLoader(com.tian.spi.PersonService.class).getExtension(extName);

拿着这个extName去我们的配置文件里找。

beijing=com.tian.spi.BeijingPersonServiceImpl
guangdong=com.tian.spi.GuangdongPersonServiceImpl

最后返回一个实现类实例。

3、调用extensionqueryCountry()方法,也就是调用我们具体实现类的方法。

搞清楚上面这套规则后,你就再也不用去关心Xxx$Adaptive里的内容了。

之前,我也刻意去B站上找了Dubbo源码分析的视频看看,结果,哎,很多乱七八糟的,Dubbo SPI机制都没有搞清楚,上来就瞎讲,然后就是各种猜测,我们猜测会调用哪个类?搞清楚上面这些SPI机制后,我们还需要猜吗?那不是一眼就能看出来吗?

标签:实战,Dubbo,SPI,PersonService,class,tian,spi,public
From: https://blog.51cto.com/u_11702014/6459163

相关文章

  • linux 脚本基础实战1
    脚本完成功能1.显示出本机的ip地址2.如果ip地址中有3这个数字,打印出当前的系统时间3.如果ip地址中不含3这个数字,批量建立用户magedu_00,magedu_01,...magedu_100,所有用户同属于magedu组4.打印/etc/passwd这个文件中可以登陆的用户(非/usr/sbin/nologin)5.yum安装ngin......
  • Spring Boot&Vue3前后端分离实战wiki知识库系统<八>--分类管理功能开发二
    接着上一次SpringBoot&Vue3前后端分离实战wiki知识库系统<七>--分类管理功能开发的分类功能继续完善。分类编辑功能优化:概述:现在分类编辑时的界面长这样:很明显目前的父分类的展现形式不太人性,这里需要指定父分类的id才可以,对于用户来说这种交互是反人道的,用户怎么知道父分类......
  • WFP必须掌握的技能之自定义控件——实战:自制上传文件显示进度按钮
    自定义控件在WPF开发中是很常见的,有时候某些控件需要契合业务或者美化统一样式,这时候就需要对控件做出一些改造。目录按钮设置圆角按钮上传文件相关定义测试代码话不多说直接看效果默认效果:上传效果:按钮设置圆角因为按钮本身没有CornerRadius属性,所以只能重写Butto......
  • 【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实
    异常传播有时候,您可能需要重新抛出捕获到的异常。这种情况通常发生在捕获到Error或RuntimeException时,因为您可能没有预料到这些异常,但在声明捕获Throwable和Exception时,它们也被包含在内了。为了解决这个问题,Guava提供了多种方法来判断异常类型并重新抛出异常。例如:try{......
  • Tomcat 入门实战(1)--简介
    Tomcat是Apache软件基金会(ApacheSoftwareFoundation)的一个开源项目,实现了Servlet及JSP规范,可以用来部署WEB应用及WebService;本文主要介绍其基本概念。1、Tomcat安装安装Tomcat之前需要先安装Java,并设置$JAVA_HOME环境变量,Linuxbash环境下可按如下方式设置:expor......
  • Zookeeper入门实战(5)-分布式锁
    在分布式环境中,当需要控制对某一资源的不同进程并发访问时就需要使用分布式锁;可以使用 ZooKeeper+Curator来实现分布式锁,本文主要介绍 Curator中分布式锁的使用,文中所使用到的软件版本:Java1.8.0_341、Zookeeper3.7.1、curator5.4.0。1、引入依赖<dependency><gro......
  • graalvm nodejs + spire office 实现office 处理
    实际上是一个比较简单的处理,主要是利用了graalvm的nodejs可以方便的使用外部jar,我们就可以使用其他nodejsweb框架,结合spireoffice实现专业可靠的office转换处理预备主要是安装graalvm以及nodejsgraalvm已经不支持包含nodejs了,需要独立安装参考对于grralvm安装使用......
  • Git实战之git工具的安装
    (Git实战之git工具的安装)一、Git介绍git是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。二、Linux环境的检查1.检查Linux系统版本[root@node~]#cat/etc/os-releaseNAME="CentOSLinux"VERSION="7(Core)"ID="centos"ID_LIKE="r......
  • Graph Neural Networks Inspired by Classical Iterative Algorithms
    目录概符号说明MotivationRobustRegularizationYangY.,LiuT.,WangY.,ZhouJ.,GanQ.,WeiZ.,ZhangZ.,HuangZ.andWipfD.Graphneuralnetworksinspiredbyclassicaliterativealgorithms.ICML,2021.概基于广义energyfunction(diffusion)的图神经网......
  • Vue入门实战05-模板语法
    Vue使用一种基于HTML的模板语法,声明式将其组件实例的数据绑定到DOM。所有Vue模板都是语法层合法的HTML,可被符合规范的浏览器和HTML解析器解析。底层机制中,Vue会将模板编译成高度优化的JavaScript代码。结合响应式系统,当应用状态变更时,Vue能够智能地推导出需要重新渲染的......