JDK与Dubbo中的SPI详细介绍

目录
  • 1、SPI简介
  • 2、JDK中的SPI
  • 3、Dubbo中的SPI
  • 4、Dubbo中扩展点使用方式
  • 5、DubboSPI中的Adaptive功能

1、SPI简介

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

2、JDK中的SPI

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

SPI遵循如下约定:

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

入门案例

模块结构如下

(1)api模块

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

package com.lagou.service;
public interface HelloService {
    String sayHello();
}

(2)impl模块

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

实现类

package com.lagou.service.impl;
import com.lagou.service.HelloService;
public class HumanHelloService implements HelloService {
    @Override
    public String sayHello() {
        return "hello world";
    }
}

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

com.lagou.service.impl.HumanHelloService

(3)main模块

提供了测试jdk-spi的的类

package com.lagou.test;
import com.lagou.service.HelloService;
import java.util.ServiceLoader;
public class JavaSpiMain {
    public static void main(String[] args) {
        final ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class);
        for (HelloService helloService : helloServices) {
            System.out.println(helloServices.getClass().getName() + ";" + helloService.sayHello());
        }
    }
}

3、Dubbo中的SPI

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

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

4、Dubbo中扩展点使用方式

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

(1)api模块

往pom.xml中导入dubbo依赖

       <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.5</version>
        </dependency>

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

package com.lagou.service;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI
public interface HelloService {
    String sayHello();
}

(2)impl模块

导入 api项目 的依赖

建立实现类

package com.lagou.service;
public class HumanHelloService implements HelloService{
    @Override
    public String sayHello() {
        return "hello 你好";
    }
}

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

human=com.lagou.service.HumanHelloService

(3)main模块

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

创建DubboSpiMain

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

package com.lagou;
import com.lagou.service.HelloService;
import org.apache.dubbo.common.extension.ExtensionLoader;
import java.util.Set;
public class DubboSpiMain {
    public static void main(String[] args) {
        // 获取扩展加载器
        ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
        // 遍历所有支持的扩展点   META-INF.dubbo
        Set<String> extensions = extensionLoader.getSupportedExtensions();
        for (String extension : extensions) {
            String result = extensionLoader.getExtension(extension).sayHello();
            System.out.println(result);
        }
    }
}

(4)dubbo自己做SPI的目的

1. JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源

2. 如果有扩展点加载失败,则所有扩展点无法使用

3. 提供了对扩展点包装的功能(Adaptive),并且还支持通过set的方式对其他的扩展点进行注入

5、DubboSPI中的Adaptive功能

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

(1)创建接口

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

package com.lagou.service;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI("human") // 这个human对应配置文件中的human
public interface HelloService {
    String sayHello();
    @Adaptive
    String sayHello(URL url);
}

(2)创建实现类

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

package com.lagou.service;
import org.apache.dubbo.common.URL;
public class HumanHelloService implements HelloService{
    @Override
    public String sayHello() {
        return "hello 你好";
    }
    @Override
    public String sayHello(URL url) {
        return "hello url";
    }
}

(3)编写DubboAdaptiveMain

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

package com.lagou;
import com.lagou.service.HelloService;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class DubboAdaptiveMain {
    public static void main(String[] args) {
        URL url = URL.valueOf("test://localhost?hello.service=human");
        HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
        String hello = adaptiveExtension.sayHello(url);
        System.out.println(hello);
    }
}

注意:

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

到此这篇关于JDK与Dubbo中的SPI详细介绍的文章就介绍到这了,更多相关JDK与Dubbo的SPI内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Dubbo3的Spring适配原理与初始化流程源码解析

    目录 引言 Spring Context Initialization FactoryBean BeanDefinition 初始化bean 解决依赖 解决属性 Dubbo Spring的一些问题及解决办法 Dubbo spring 2.7 初始化过程 Dubbo spring 3的初始化过程 属性占位符解决失败 ReferenceBean被过早初始化问题 Reference注解可能出现@Autowire注入失败的问题 引言 Dubbo 国内影响力最大的开源框架之一,非常适合构建大规模微服务集群

  • java开发分布式服务框架Dubbo原理机制详解

    目录 前言 Dubbo框架有以下部件 Consumer Provider Registry Monitor Container 架构 高可用性 框架设计 服务暴露过程 服务消费过程 前言 在介绍Dubbo之前先了解一下基本概念: Dubbo是一个RPC框架,RPC,即Remote Procedure Call(远程过程调用),相对的就是本地过程调用,在分布式架构之前的单体应用架构和垂直应用架构运用的都是本地过程调用.它允许程序调用另外一个地址空间(通常是网络共享的另外一台机器)的过程或函数,并且

  • 关于Dubbo初始问题

    目录 Dubbo架构 节点角色说明 过程 Zookeeper安装 Dubbo快速入门 jar包依赖 开始配置dubbo 配置zk 总结 Dubbo架构 消费者:调用提供者,但是不能直接调,需要借助注册中心 节点角色说明 Provider:暴露服务的服务提供方 Container: 服务运行容器 Consumer: 调用远程服务的服务消费方 Registry:服务注册与发现的注册中心 Monitor:统计服务的调用次数和调用时间的监控中心 过程 0start:服务的提供者要运行在一个容器里面,比如

  • maven中pom.xml详细介绍

    POM 代表工程对象模型.它是使用 Maven 工作时的基本组建,是一个 xml 文件.它被放在工程根目录下,文件命名为 pom.xml. POM 包含了关于工程和各种配置细节的信息,Maven 使用这些信息构建工程. POM 也包含了目标和插件.当执行一个任务或者目标时,Maven 会查找当前目录下的 POM,从其中读取所需要的配置信息,然后执行目标.能够在 POM 中设置的一些配置如下: project dependencies plugins goals build profiles pr

  • Python 中pandas.read_excel详细介绍

    Python 中pandas.read_excel详细介绍 #coding:utf-8 import pandas as pd import numpy as np filefullpath = r"/home/geeklee/temp/all_gov_file/pol_gov_mon/downloads/1.xls" #filefullpath = r"/home/geeklee/temp/all_gov_file/pol_gov_mon/downloads/26368f3

  • Spring在web.xml中的配置详细介绍

    Spring在web.xml中的配置详细介绍 前言      在实际项目中spring的配置文件applicationcontext.xml是通过spring提供的加载机制自动加载到容器中.在web项目中,配置文件加载到web容器中进行解析.目前,spring提供了两种加载器,以供web容器的加载:一种是ContextLoaderListener,另一种是ContextLoaderServlet.这两种在功能上完全相同,只是前一种是基于Servlet2.3版本中新引入的Listener接口实现,

  • Docker中的镜像详细介绍

    Docker中的镜像详细介绍 Docker镜像可以理解为运行在Docker容器中的一个组件,本节将会带领大家学会Docker镜像的下载.搜索.查看.添加删除等一系列操作.Docker在运行容器前需要本地存在对应的镜像,如果镜像不存在本地,Docker会尝试从默认的远程仓库Docker Hub下载. 一.获取镜像:  1.Docker从网络上下载镜像的格式为: docker pull [NAME]:[TAG] 其中TAG即为标签,如果不指定标签的话,那么默认会下载最新版本的镜像. 比如从Docke

  • Spring中@Transactional用法详细介绍

    Spring中@Transactional用法详细介绍 引言: 在spring中@Transactional提供一种控制事务管理的快捷手段,但是很多人都只是@Transactional简单使用,并未深入了解,其各个配置项的使用方法,本文将深入讲解各个配置项的使用. 1.  @Transactional的定义 Spring中的@Transactional基于动态代理的机制,提供了一种透明的事务管理机制,方便快捷解决在开发中碰到的问题.在现实中,实际的问题往往比我们预期的要复杂很多,这就要求对@Tr

  • Java中ArrayList类详细介绍

    Java中ArrayList类详细介绍 ArrayList是一个可变长度数组,它实现了List接口,因此它也可以包含重复元素和Null元素,也可以任意的访问和修改元素,随着向 ArrayList 中不断添加元素,其容量也自动增长.不过ArrayList是非同步(同步的意思是如果多个线程同时访问一个实例,任何一个线程对实例做了修改之后,其他线程所访问到的实例应该是修改过的最新的实例)的, 我们经常使用List list = Collections.synchronizedList(new Arra

  • Android Service中方法使用详细介绍

     service作为四大组件值得我们的更多的关注 在Android中,Activity主要负责前台页面的展示,Service主要负责需要长期运行的任务.例如,一个从service播放音乐的音乐播放器,应被设置为前台运行,因为用户会明确地注意它的运行.在状态栏中的通知可能会显示当前的歌曲并且允许用户启动一个activity来与音乐播放器交互. Service的两种实现形式 1.非绑定 通过调用应用程序组件(例如Activity)的startService()方法来启动一个服务.一旦启动,服务就会在

  • Java编程中的构造函数详细介绍

    本文主要是为新手.对java语言感兴趣的人和那些没有系统学习过java基础知识的人进行一个总结,在文章中对构造函数进行了较为详细的说明和讨论,也包含了我个人对于java面向对象中构造函数的一些看法.希望走在java学习道路上的同行者可以有一个较为清晰的认知和理解.当然仅为个人观点,水平有限,不足之处,还请大家多多指出,互相交流学习. 1.构造函数的概念 很多java新手谈到构造函数就会犯晕,我们先来看看什么是构造函数. 首先,构造函数是函数的一种特殊形式,特殊在哪里?构造函数中不需要定义返回类型

  • MyBatis框架中mybatis配置文件详细介绍

    一.注册DB连接四要素属性文件 <propertiesresource="jdbc.properties"/> 二.将指定包中所有类的简单类名当作其别名 <typeAliases> <packagename="com.bjpowernode.beans"/> </typeAliases> 三.配置运行环境 <environmentsdefault="testEM"> <enviro

  • Python中栈的详细介绍

    目录 1.问题描述 2.解决方案 3.结语 本文转自公众号:"算法与编程之美" 1.问题描述 Python中数据类型有列表,元组,字典,队列,栈,树等等.像列表,元组这样的都是python内置数据结构:栈,队列这些都是需要我们自己去定义的. 栈是一种只允许在一端插入和取出的数据结构,这一端通常被叫做栈顶,另一端叫栈底,没有数据的叫空栈.这种数据类型由于是我们自己进行定义,所以有很多功能都需要自己写出相应函数来实现.所以我们看看这里的功能. 2.解决方案 栈的基本操作大概有:生成栈,入栈

随机推荐