锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. Dubbo中SPI机制和拦截器

Dubbo中SPI机制和拦截器

0
  • 软件开发
  • 发布于 2024-08-19
  • 1 次阅读
黄健
黄健

SPI

SPI简介

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

JDK中的SPI


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

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

简单实现

  1. 创建maven父工程
  2. 创建接口子工程,定义接口
public interface HelloService {
   
    String sayHello();
}
  1. 创建实现类子工程,定义实现类
public class DogHelloService implements HelloService {
   
    @Override
    public String sayHello() {
   
        return "wang wang";
    }
}
public class HumanHelloService implements HelloService {
   
    @Override
    public String sayHello() {
   
        return "hello human";
    }
}
  1. 实现类子工程resources下创建META-INF/services目录,添加接口的全限定类名的文件


5. 创建主启动类

public class HelloMain {
   
    public static void main(String[] args) {
   
        final ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class);
        for (HelloService service : helloServices) {
   
            System.out.println(service.sayHello());
        }
    }
}
  1. 运行测试

注意: 如果编译时提示子工程之间的依赖失败,只需要将父工程clean后重新install

Failed to execute goal on project XXXX-api: Could not resolve dependencies for pro

参考这个链接

Dubbo中的SPI

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

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

Dubbo中扩展点使用方式

使用三个项目来演示Dubbo中扩展点的使用方式,一个主项目main,一个服务接口项目api,一个服务实现项目impl。
api项目创建
(1)导入坐标 dubbo

    <dependencies>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.8</version>           
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-zookeeper</artifactId>
            <version>2.7.8</version>           
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-dubbo</artifactId>
            <version>2.7.8</version>           
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-remoting-netty4</artifactId>
            <version>2.7.8</version>           
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-serialization-hessian2</artifactId>
            <version>2.7.8</version>           
        </dependency>

    </dependencies>

(2)创建接口
在接口上 使用@SPI

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

impl项目创建
(1)导入 api项目 的依赖

        <dependency>
            <groupId>con.elvis</groupId>
            <artifactId>demo-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

(2)建立实现类,为了表达支持多个实现的目的,这里分别创建两个实现。分别为
HumanHelloService 和DogHelloService 。

public class HumanHelloService implements HelloService {
   
    @Override
    public String sayHello() {
   
        return "hello human";
    }
}
public class DogHelloService implements HelloService {
   
    @Override
    public String sayHello() {
   
        return "wang wang";
    }
}

(3)SPI进行声明操作,在resources 目录下创建目录META-INF/dubbo 目录,在目录下创建名称为com.elvis.service.HelloService的文件,文件内部配置两个实现类名称和对应的全
限定名:

human=com.elvis.service.impl.HumanHelloService
dog=com.elvis.service.impl.DogHelloService

main项目创建
(1)导入坐标 接口项目 和 实现类项目

    <dependencies>
        <dependency>
            <groupId>con.elvis</groupId>
            <artifactId>demo-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>con.elvis</groupId>
            <artifactId>demo-api-impl</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

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

public class DubboSpiMain {
   
    public static void main(String[] args) {
   
        final ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
        Set<String> supportedExtensions = extensionLoader.getSupportedExtensions();
        for (String extension : supportedExtensions) {
   
            HelloService service = extensionLoader.getExtension(extension);
            System.out.println(service.sayHello());
        }
    }
}


(3)dubbo自己做SPI的目的

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

Dubbo SPI中的Adaptive功能

  Dubbo中的Adaptive功能,主要解决的问题是如何动态的选择具体的扩展点。通过getAdaptiveExtension 统一对指定接口对应的所有扩展点进行封装,通过URL的方式对扩展点来进行动态选择。 (dubbo中所有的注册信息都是通过URL的形式进行处理的。)这里同样采用相同的方式进行实现。
(1)创建接口
api中的HelloService 扩展如下方法, 与原先类似,在sayHello中增加Adaptive 注解,并且在参数中提供URL参数.注意这里的URL参数的类为org.apache.dubbo.common.URL其中@SP可以指定一个字符串参数,用于指明该SPI的默认实现。

@SPI("human")
public interface HelloService {
   
    String sayHello();

    @Adaptive
    String sayHello(URL url);
}

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

public class HumanHelloService implements HelloService {
   
    @Override
    public String sayHello() {
   
        return "hello human";
    }

    @Override
    public String sayHello(URL url) {
   
        return "human url " + url;
    }
}

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

public class DubboUrlMain {
   

    public static void main(String[] args) {
   
    	// 匹配的规则为接口名大写字母转换成小写,单词直接使用.分隔,=号后面跟要调用的实例名,就是在META-INF中文件的内容如下:
        //human=com.elvis.service.impl.HumanHelloService
        // dog=com.elvis.service.impl.DogHelloService
        // 调用human 则可以使用下面的URL
        // test://localhost/hello?hello.service=human
        // 如果不指定实例名则默认使用SPI接口中指定的实例
        URL url  = URL.valueOf("test://localhost/hello?hello.service=");
        HelloService service = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
        System.out.println(service.sayHello(url));
    }
}

测试

注意:

  • 因为在这里只是临时测试,所以为了保证URL规范,前面的信息均为测试值即可,关键的点在于hello.service 参数,这个参数的值指定的就是具体的实现方式。关于为什么叫hello.service 是因为这个接口的名称,其中后面的大写部分被dubbo自动转码为. 分割。
  • 通过getAdaptiveExtension 来提供一个统一的类来对所有的扩展点提供支持(底层对所有的扩展点进行封装)。
  • 调用时通过参数中增加URL 对象来实现动态的扩展点使用。
  • 如果URL没有提供该参数,则该方法会使用默认在SPI 注解中声明的实现。

Dubbo调用时拦截操作

  与很多框架一样,Dubbo也存在拦截(过滤)机制,可以通过该机制在执行目标程序前后执行我们指定的代码。
Dubbo的Filter机制,是专门为服务提供方和服务消费方调用过程进行拦截设计的,每次远程方法执行,该拦截都会被执行。这样就为开发者提供了非常方便的扩展性,比如为dubbo接口实现ip白名单功能、监控功能 、日志记录等。
步骤如下:
(1)实现org.apache.dubbo.rpc.Filter 接口

@Activate(group = {
   CommonConstants.CONSUMER, CommonConstants.PROVIDER})
public class DubboInvokeFilter implements Filter {
   
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
   
        long   startTime  = System.currentTimeMillis();
        try {
   
            // 执行方法
            return  invoker.invoke(invocation);
        } finally {
   
            System.out.println("invoke time:"+(System.currentTimeMillis()-startTime) + "毫秒");
        }
    }
}

(2)使用org.apache.dubbo.common.extension.Activate 接口进行对类进行注册 通过group 可以
指定生产端 消费端 如:

@Activate(group = {
   CommonConstants.CONSUMER, CommonConstants.PROVIDER})

(3)在META-INF.dubbo 中新建org.apache.dubbo.rpc.Filter 文件,并将当前类的全名写入

timerFilter=com.elvis.filter.DubboInvokeFilter

(4) 需要使用拦截器的模块引入依赖即可

        <dependency>
            <groupId>com.elvis</groupId>
            <artifactId>dubbo-spi-filter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

**注意:**一般类似于这样的功能都是单独开发依赖的,所以再使用方的项目中只需要引入依赖,在调用接口时,该方法便会自动拦截。

原文链接: https://blog.csdn.net/Kiven_ch/article/details/117885468

标签: #Dubbo 7 #软件开发 1171
相关文章

万字:支付“核心系统”详解 2024-11-02 15:33

专栏作者:隐墨星辰 \| 主编:陈天宇宙 这篇文章也尝试化繁为简,探寻支付系统的本质,讲清楚在线支付系统最核心的一些概念和设计理念。 虽然支付行业已经过了风头最劲的时光,但跨境支付仍然在蓬勃发展,每年依然有很多新人进入这个行业,这篇文章尝试为这些刚入行的新人提供一点帮助。 文章只介绍一些支付行业十几

资深支付架构师视角:实战从问题定义到代码落地的完整套路 2024-11-02 15:33

前言 今天从一个实际案例入手,介绍站在架构师的角度,如何识别并定义问题,提炼需求,技术方案选型,再到详细设计,最后利用AI的能力协助写出核心的代码,验证与调优。 解决问题存在一定的模式,也可以称之为框架,总结出自己的思考和解题框架,以后再碰到同类型的问题就可以如庖丁解牛一样容易。 很多年前,我写代码

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

设计模式第16讲——迭代器模式(Iterator) 2024-10-08 11:24

一、什么是迭代器模式 迭代器模式是一种行为型设计模式,它提供了一种统一的方式来访问集合对象中的元素,而不是暴露集合内部的表示方式。简单地说,就是将遍历集合的责任封装到一个单独的对象中,我们可以按照特定的方式访问集合中的元素。 二、角色组成 抽象迭代器(Iterator):定义了遍历聚合对象所需的方法

vue2路由和vue3路由区别及原理 2024-10-08 11:24

一、Vue2 与 Vue3 路由的区别 1. 创建路由实例方式的不同 Vue 2 中,通过 Vue.use() 注册路由插件,并通过 new VueRouter() 来创建路由实例。 import Vue from 'vue';import VueRouter from 'vue-router';i

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.