• JDK中的SPI 与 Dubbo中的SPI


    1、SPI简介

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

    2、JDK中的SPI

             Java中如果想要使用SPI功能,先提供标准服务接口,然后再提供相关接口实现和调用者。这样就可以通过SPI机制中约定好的信息进行查询相应的接口实现。

    SPI遵循如下约定:

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

    入门案例

      模块结构如下

     (1)api模块

      此模块创建了一个接口,供后续模块使用

    1. package com.lagou.service;
    2. public interface HelloService {
    3. String sayHello();
    4. }

    (2)impl模块

      此模块中提供了api模块接口的实现类,并且在resource目录下,创建了配置文件。

     实现类

    1. package com.lagou.service.impl;
    2. import com.lagou.service.HelloService;
    3. public class HumanHelloService implements HelloService {
    4. @Override
    5. public String sayHello() {
    6. return "hello world";
    7. }
    8. }

    配置文件(名称为接口全限定名, 内容为接口实现类全限定类名

    com.lagou.service.impl.HumanHelloService

    (3)main模块

       提供了测试jdk-spi的的类

    1. package com.lagou.test;
    2. import com.lagou.service.HelloService;
    3. import java.util.ServiceLoader;
    4. public class JavaSpiMain {
    5. public static void main(String[] args) {
    6. final ServiceLoader helloServices = ServiceLoader.load(HelloService.class);
    7. for (HelloService helloService : helloServices) {
    8. System.out.println(helloServices.getClass().getName() + ";" + helloService.sayHello());
    9. }
    10. }
    11. }

    3、Dubbo中的SPI

            dubbo中大量的使用了SPI来作为扩展点,通过实现同一接口的前提下,可以进行定制自己的实现类。比如比较常见的协议,负载均衡,都可以通过SPI的方式进行定制化,自己扩展。Dubbo中已经存在的所有已经实现好的扩展点。

     下图中则是Dubbo中默认提供的负载均衡策略。

     

    4、Dubbo中扩展点使用方式

            我们使用三个项目来演示Dubbo中扩展点的使用方式,一个主项目main,一个服务接口项目api,一个服务实现项目impl。

    (1)api模块

      往pom.xml中导入dubbo依赖

    1. <dependency>
    2. <groupId>org.apache.dubbogroupId>
    3. <artifactId>dubboartifactId>
    4. <version>2.7.5version>
    5. dependency>

    创建接口,并在接口上添加上@SPI注解

    1. package com.lagou.service;
    2. import org.apache.dubbo.common.URL;
    3. import org.apache.dubbo.common.extension.Adaptive;
    4. import org.apache.dubbo.common.extension.SPI;
    5. @SPI
    6. public interface HelloService {
    7. String sayHello();
    8. }

    (2)impl模块

     导入 api项目 的依赖

     建立实现类

    1. package com.lagou.service;
    2. public class HumanHelloService implements HelloService{
    3. @Override
    4. public String sayHello() {
    5. return "hello 你好";
    6. }
    7. }

      SPI进行声明操作,在resources 目录下创建目录META-INF/dubbo 目录,在目录下创建名称为接口的全限定类名的文件,文件内部配置实现类名称和对应的全限定名: 

    human=com.lagou.service.HumanHelloService

    (3)main模块

      导入坐标、接口项目和实现类项目

      创建DubboSpiMain

            和原先调用的方式不太相同, dubbo 有对其进行自我重新实现 需要借助ExtensionLoader,创建新的运行项目。这里demo中的示例和java中的功能相同,查询出所有的已知实现,并且调用

    1. package com.lagou;
    2. import com.lagou.service.HelloService;
    3. import org.apache.dubbo.common.extension.ExtensionLoader;
    4. import java.util.Set;
    5. public class DubboSpiMain {
    6. public static void main(String[] args) {
    7. // 获取扩展加载器
    8. ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
    9. // 遍历所有支持的扩展点 META-INF.dubbo
    10. Set extensions = extensionLoader.getSupportedExtensions();
    11. for (String extension : extensions) {
    12. String result = extensionLoader.getExtension(extension).sayHello();
    13. System.out.println(result);
    14. }
    15. }
    16. }

    (4)dubbo自己做SPI的目的

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

    5、Dubbo SPI中的Adaptive功能

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

    (1)创建接口

            api中的HelloService 扩展如下方法, 与原先类似,在sayHello中增加Adaptive 注解,并且在参数中提供URL参数.注意这里的URL参数的类为org.apache.dubbo.common.URL,其中@SP可以指定一个字符串参数,用于指明该SPI的默认实现。

    1. package com.lagou.service;
    2. import org.apache.dubbo.common.URL;
    3. import org.apache.dubbo.common.extension.Adaptive;
    4. import org.apache.dubbo.common.extension.SPI;
    5. @SPI("human") // 这个human对应配置文件中的human
    6. public interface HelloService {
    7. String sayHello();
    8. @Adaptive
    9. String sayHello(URL url);
    10. }

    (2)创建实现类

      与上面Service实现类代码相似,只需增加URL形参即可

    1. package com.lagou.service;
    2. import org.apache.dubbo.common.URL;
    3. public class HumanHelloService implements HelloService{
    4. @Override
    5. public String sayHello() {
    6. return "hello 你好";
    7. }
    8. @Override
    9. public String sayHello(URL url) {
    10. return "hello url";
    11. }
    12. }

    (3)编写DubboAdaptiveMain 

    最后在获取的时候方式有所改变,需要传入URL参数,并且在参数中指定具体的实现类参数

    1. package com.lagou;
    2. import com.lagou.service.HelloService;
    3. import org.apache.dubbo.common.URL;
    4. import org.apache.dubbo.common.extension.ExtensionLoader;
    5. public class DubboAdaptiveMain {
    6. public static void main(String[] args) {
    7. URL url = URL.valueOf("test://localhost?hello.service=human");
    8. HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
    9. String hello = adaptiveExtension.sayHello(url);
    10. System.out.println(hello);
    11. }
    12. }

    注意:

    • 因为在这里只是临时测试,所以为了保证URL规范,前面的信息均为测试值即可,关键的点在于hello.service 参数,这个参数的值指定的就是具体的实现方式。关于为什么叫hello.service 是因为这个接口的名称,其中后面的大写部分被dubbo自动转码为. 分割。
    • 通过getAdaptiveExtension 来提供一个统一的类来对所有的扩展点提供支持(底层对所有的扩展点进行封装)。
    • 调用时通过参数中增加URL 对象来实现动态的扩展点使用。
    • 如果URL没有提供该参数,则该方法会使用默认在SPI 注解中声明的实现。
  • 相关阅读:
    网页篡改事件应急案例
    Sodinokibi解密工具,可解密一千七百多个变种
    Shein寻求在美IPO获900亿美元估值,能成功吗?
    Spring学习篇(四)
    【EMC专题】电磁兼容研究涉及的领域
    PHP去除BOM头的方法
    从初级程序员到CEO,汤鹏与时代碰撞出的那些“火花”
    OpenCV图像裁剪:使用&运算符在OpenCV图像裁剪时进行边界检查
    redis和selery相关知识点
    vue.runtime.esm.js?c320:4625 [Vue warn]: Failed to resolve directive: lazy
  • 原文地址:https://blog.csdn.net/weixin_52851967/article/details/126328986