从零开始学Dubbo-基础篇-SPI使用

538 阅读4分钟

SPI简介

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 目前有不少框架用它 来做服务的扩展发现,简单来说,它就是一种动态替换发现的机制。使用SPI机制的优势是实现解耦, 使得第三方服务模块的装配控制逻辑与调用者的业务代码分离。

Java SPI

首先了解一下Java的SPI机制:

SPI遵循如下约定:

  • 1、当服务提供者提供了接口的一种具体实现后,在META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
  • 2、接口实现类所在的jar包放在主程序的classpath中;
  • 3、主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
  • 4、SPI的实现类必须携带一个无参构造方法;

JavaSPI 案例

1.创建api模块制定规范

public interface HelloSpi {
    String sayHello();
}

2.创建impl模块实现api接口

依赖api模块 这边先创建两个类一个man,一个woman 分别实现sayHello接口

public class ManSpi implements HelloSpi {
    @Override
    public String sayHello() {
        return "man say hello";
    }
}
public class WomanSpi implements HelloSpi {
    @Override
    public String sayHello() {
        return "women say hello";
    }
}

然后再resources目录下创建META-INF/services目录并且创建和接口类路径同名的文件 image.png

然后在文件配置实现类的全路径类名

image.png

创建测试模块

依赖api以及impl模块

public class JavaSpiTest {
    public static void main(String[] args) {
        //获取所有类
        ServiceLoader<HelloSpi> serviceLoader=ServiceLoader.load(HelloSpi.class);
        //遍历输出
        for (HelloSpi helloSpi : serviceLoader) {
            System.out.println(helloSpi.getClass().getName()+":"+helloSpi.sayHello());
        }
    }
}

测试

首先配置文件只配置ManSpi

image.png

运行看执行结果

image.png

然后将配置改为WomanSpi

image.png

运行看执行结果

image.png

如果全部配置也是可以的

image.png

Dubbo SPI

dubbo为什么要写自己的SPI

    1. JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加 载,会很浪费资源
    1. 如果有扩展点加载失败,则所有扩展点无法使用
    1. 提供了对扩展点包装的功能(Adaptive),并且还支持通过set的方式对其他的扩展点进行注入

1.创建api模块制定规范

需要在接口上加上SPI注解标注

@SPI
public interface HelloService {
    String sayHello();
}

2.创建impl模块实现api接口

需要引入api模块,man和woman分别实现接口

public class ManHello implements HelloService {
    @Override
    public String sayHello() {
        return "dubbo man say hello";
    }
}
public class WomanHello implements HelloService {
    @Override
    public String sayHello() {
        return "dubbo woman say hello";
    }
}

在resources目录下新建META-INF.dubbo目录,并以接口全路径类名生成文件

image.png

在文件中配置key=value key相当于id value相当于实现,当然也可以只配置value,这边配置key方便@Adaptive使用

image.png

创建测试模块

这边用到的是dubbo自己的加载器

public class DubboSpiTest {
    public static void main(String[] args) {
        //扩展加载器
        ExtensionLoader<HelloService> extensionLoader=ExtensionLoader.getExtensionLoader(HelloService.class);
        //遍历扩展点
        final Set<String> supportedExtensions = extensionLoader.getSupportedExtensions();
        for (String supportedExtension : supportedExtensions) {
            final HelloService extension = extensionLoader.getExtension(supportedExtension);
            System.out.println(extension.sayHello());
        }
    }
}

执行查看结果

image.png

Dubbo SPI @Adaptive

Dubbo中的Adaptive功能,主要解决的问题是如何动态的选择具体的扩展点。通过 getAdaptiveExtension 统一对指定接口对应的所有扩展点进行封装,通过URL的方式对扩展点来进行 动态选择。 (dubbo中所有的注册信息都是通过URL的形式进行处理的。)

1.在api中新增接口

@SPI
public interface HelloService {
    @Adaptive
    String sayHello(URL url);
}

2.在impl中新增实现

public class ManHello implements HelloService {
    @Override
    public String sayHello(URL url) {
        return "dubbo URL man say hello";    }
}
public class WomanHello implements HelloService {
    @Override
    public String sayHello(URL url) {
        return "dubbo URL woman say hello";
    }
}

3.测试模块新增测试方法

此处为测试 所以URL前面的部分可以随意填写,?后面的部分是HelloService首字母小写,后面首字母.小写组成,=号后面是刚刚配置文件里的key,代表所要引用的对象

public class DubboAdaptiveTest {
    public static void main(String[] args) {
        //hello.service-HelloService
        URL url=URL.valueOf("test://localhost/hello?hello.service=man");
        final HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
        final String s = adaptiveExtension.sayHello(url);
        System.out.println(s);
    }
}

先配置man执行看结果

image.png

再配置woman

image.png

如果不配置会怎么样?执行一下:

image.png 会报错,此时可以通过设置默认来解决:

image.png

image.png

image.png

参数的优先级是高于默认的。