前言:开闭原则一直是软件开发领域中所追求的,开闭原则中的"开"是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的,“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代码。对于一个高度集成化的、成熟、稳健的系统来讲,永远不是封闭、固守的,它需要向外提供一定的可扩展的能力,外部的实现类或者jar包都可以调用它。在面向对象的开发领域中,接口是对系统功能的高度抽象,因为SPI可谓是"应运而生",本篇博客就开始走进SPI,探究java自身的SPI和Dubbo的SPI到底是什么原理

目录

一:SPI是什么

二:jdk的SPI

三:dubbo的SPI

四:总结

正文

一:SPI是什么?

spi全称英文是service provider Interface,翻译成中文也就是服务提供接口,在jdk 1.6开始,就已经提供了SPI.它的使用比较简单。即在项目的类路径下提供一个META/services/xx文件,配置一个文件,文件名为接口的全路径的名称,内容为具体的实现类全路径名。jdk将会使用ServiceLoader.load()方法去解析和加载接口和其中的实现类,按需执行不同的方法。

举个简单的例子:在jdbc中,jdk提供了driver(数据库)接口,但是不同的厂商实现起来的方式不同,比如mysql、oracle、sqlLite等厂商底层的实现逻辑都是不同的,因此在对数据库驱动driver实现方式上,可以采用SPI机制。比如在mysql-contactor.jar包中会在META/services路径下,这里相当于扩展了java.sql.Driver接口,jdk会在META/services路径下扫描该文件,然后加载mysql的diver实现类com.mysql.cj.jdbc.Driver,就相当于扩展了Driver的接口能力,按需加载mysql的实现类。oracle的连接jar包会有oracle的配置文件,这样不同的数据库根据自身的不同逻辑按需扩展了Driver的能力,这就是SPI的最大好处。

java.sql.Driver的内容:

二:java的SPI机制

从jdk1.6开始,java就提供了spi机制的支持,接下来我们就从一个例子来说明jdk的spi是如何实现的?

2.1:设计一个接口

public interface Animal {
void sound();
}

2.2:有两个实现类:

public class Cat implements Animal {
public void sound() {
System.out.println("小猫在叫");
}
}
public class Dog implements Animal {
public void sound() {
System.out.println("小狗在叫");
}
}

2.3:配置META-INF类

2.4:读取配置

    public static void main(String[] args) {
ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
final Iterator<Animal> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
Animal next = iterator.next();
next.sound();
}
}
}

2.5:原理

在上面的例子中:定义了一个接口Animal,然后有两个实现类:Cat和Dog,在META-INF的文件目录下,两个接口都进行了相关的配置,接口实现类模块要同时加载两个类,具体的调用逻辑在客户端的ServiceLoader中来通过迭代器遍历来调用具体的配置实现类,那么代码具体的原理是什么呢?跟着我一起走进源码来分析一下jdk:

在ServiceLoader的load方法中首先会获取上下文类加载器,然后构造一个ServiceLoader,在ServiceLoader中有一个懒加载器,懒加载器会通过BufferedReader来从META-INF/services路径下读取对应的接口名的全路径名文件,也就是我们配置的文件,然后通过文件的类解析器读取文件中的内容,再通过类加载器加载类的全路径

仔细分析下java的spi具有以下缺点:

①无法按需加载。虽然 ServiceLoader 做了延迟载入,使用了LazyIterator,但是基本只能通过遍历全部获取,接口的实现类得全部载入并实例化一遍。如果你并不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,假如我只需要其中一个,其它的并不需要这就形成了一定的资源消耗浪费

②不具有IOC的功能,假如我有一个实现类,如何将它注入到我的容器中呢,类之间依赖关系如何完成呢?

③serviceLoader不是线程安全的,会出现线程安全的问题

三:dubbo的SPI

dubbo在原有的spi基础上主要有以下的改变,①配置文件采用键值对配置的方式,使用起来更加灵活和简单  增强了原本SPI的功能,使得SPI具备ioc和aop的功能,这在原本的java中spi是不支持的。dubbo的spi是通过ExtensionLoader来解析的,通过ExtensionLoader来加载指定的实现类,配置文件的路径在META-INF/dubbo路径下,我们通过一个例子来了解dubbo的SPI运行机制:

3.1:dubbo的负载均衡机制其中就采用了spi机制,选择哪个负载均衡策略是通过@SPI注解来实现的:

利用ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(name)来获取具体的LoadBalance的实现类,其中name是对应配置文件(见下文)中的键;

其中用了@SPI来指定了dubbo的负载均衡策略为随机(random),我们再来了解一下@SPI注解和@Adaptive是如何工作的?

3.2:在dubbo的META_INF.dubbo.internal路径下存在一个文件:

com.alibaba.dubbo.rpc.cluster.LoadBalance文件,文件内容是这样的:

可以看出dubbo的spi配置是采用键值对的方式,键值对最大的好处就是可以以键来获取值,取值比较简单和方便。这点和java的spi配置方式是不同的,java的spi只有全路径名;

3.3:@SPI和@Adaptive注解的作用是什么?

Dubbo通过注解@Adaptive作为标记实现了一个适配器类,dubbo将会为这个类动态生成代理对象;ExtensionLoader中获取默认实现类或者通过实现类名称(由@SPI注解指定的名称)来获取实现类

为什么会出现@Adaptive这个注解呢?主要原因是因为dubbo的加载扩展了是从配置文件加载的,是很动态的,但是实现类却要固定写死或者灵活实现,所以就得区分开。用@Adaptive就是表示由框架自己生成,不需要人为实现.

在dubbo加载SPI时会动态创建SPI Adaptive实现ExtensionLoader。从URL获取密钥,该密钥将通过@Adaptive由接口方法定义的注释提供

3.4:dubbo的spi读取配置和实现类原理

3.4.1:从解析加载配置类的源码开始分析

ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE)

上面的源码比较简单,首先是根据传入的接口从缓存(一个以class为键,ExtensionLoader为值的concurrentHashMap)中获取,如果拿不到就放入到缓存中;逻辑比较简单,这里就不做详细分析了。接下来主要是分析:ExtensionLoader.getExtension(name)

我们来看下具体的createExtension方法的源码:

createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:

①通过 getExtensionClasses 获取所有的拓展类,也就是所有META-INF下的配置文件中的键值对名

②通过反射创建拓展对象

③向拓展对象中注入依赖

④将拓展对象包裹在相应的 Wrapper 对象中,后面需要从wrapper中取

来具体看一下dubbo是如何解析配置文件的:

上面可以看出三个路径,这和我们刚才上面看到的路径是一致的,dubbo就是读取该路径下的的文件 

加载配置文件下的文件内容,也就是上面的com.alibaba.dubbo.rpc.cluster.LoadBalance文件

3.5:dubbo的IOC机制

dubbo的IOC是通过setter方法来实现注入的,通过遍历对象实例的所有方法,找到其setter方法在进行截取,从objectFactory中获取扩展类再进行反射执行。这样的话,就算实现实例中有依赖的扩展实例,都可以注入完成,是dubbo的IOC体现。ojectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。

四:总结

本篇博客简单分别介绍了 Java SPI 与 Dubbo SPI 用法,java的spi举了个简单的例子来进行了说明。并仔细分析了jdk的spi不足,dubbo是如何面对jdk的不足之处,然后自己定制开发出一套更加合理和更好的dubbo自我实现。以及详细分析了 Dubbo SPI 的加载拓展类的过程和源码的分析。其中可以看出来dubbo中对于缓存和反射的利用是相当之多的.SPI是软件设计中高扩展性的一个体现,通过spi机制可以灵活地实现厂商的规范订制和不同企业的具体规范自己实现.高度扩展了原程序,使得我们设计出来的程序更加具有扩展力。

 

jdk和dubbo的SPI机制的更多相关文章

  1. Dubbo的SPI机制与JDK机制的不同及原理分析

    从今天开始,将会逐步介绍关于DUbbo的有关知识.首先先简单介绍一下DUbbo的整体概述. 概述 Dubbo是SOA(面向服务架构)服务治理方案的核心框架.用于分布式调用,其重点在于分布式的治理. 简 ...

  2. 面试常问的dubbo的spi机制到底是什么?

    前言 dubbo是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力.作为spring cloud alibaba体系中重要的一部分,随着spring cloud alibaba在 ...

  3. dubbo的spi机制

    SPI SPI是一种扩展机制,在java中SPI机制被广泛应用,比如Spring中的SpringServletContainerInitializer 使得容器启动的时候SpringServletCo ...

  4. Dubbo剖析-SPI机制

    文章要点: 1.什么是SPi 2.Dubbo为什么要实现自己的SPi 3.Dubbo的IOC和AOP 4.Dubbo的Adaptive机制 5.Dubbo动态编译机制 6.Dubbo与Spring的融 ...

  5. dubbo源码分析1——SPI机制的概要介绍

    插件机制是Dubbo用于可插拔地扩展底层的一些实现而定制的一套机制,比如dubbo底层的RPC协议.注册中心的注册方式等等.具体的实现方式是参照了JDK的SPI思想,由于JDK的SPI的机制比较简单, ...

  6. dubbo源码分析01:SPI机制

    一.什么是SPI SPI全称为Service Provider Interface,是一种服务发现机制,其本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件.这样可以在运行时,动态为 ...

  7. Java是如何实现自己的SPI机制的? JDK源码(一)

    注:该源码分析对应JDK版本为1.8 1 引言 这是[源码笔记]的JDK源码解读的第一篇文章,本篇我们来探究Java的SPI机制的相关源码. 2 什么是SPI机制 那么,什么是SPI机制呢? SPI是 ...

  8. Dubbo中SPI扩展机制解析

    dubbo的SPI机制类似与Java的SPI,Java的SPI会一次性的实例化所有扩展点的实现,有点显得浪费资源. dubbo的扩展机制可以方便的获取某一个想要的扩展实现,每个实现都有自己的name, ...

  9. dubbo 的 spi 思想是什么?

    面试题 dubbo 的 spi 思想是什么? 面试官心理分析 继续深入问呗,前面一些基础性的东西问完了,确定你应该都 ok,了解 dubbo 的一些基本东西,那么问个稍微难一点点的问题,就是 spi, ...

随机推荐

  1. 【题解】GREWords(AC自动机)

    [题解]GREWords(AC自动机) SP9941 GRE - GRE Words 题目大意: 给定一个由字符串构成的序列,不同位置的字符串有自己权值.现在让你选出一个子序列,使得在这个子序列中,前 ...

  2. status100到500http响应对应状态解释

    1xx-信息提示 这些状态代码表示临时的响应.客户端在收到常规响应之前,应准备接收一个或多个1xx响应. 100-继续. 101-切换协议. 2xx-成功 这类状态代码表明服务器成功地接受了客户端请求 ...

  3. my_mysql

    ###一键偷懒YUM安装MySQbL### 1.安装mysql数据库 #yum install -y mariadb-server  mariadb 2.登录mysql数据库常用选项 -h:指定服务端 ...

  4. 【自建gitlab服务器】gitlab内存持续增大,出现502错误的解决办法

    首先说明笔者的服务器环境,阿里云服务器:8G内存,2核.自从团队运维小伙伴搭建了gitlab之后,git push 代码时不时的就很卡,也经常出现 gitlab 反应超时,返回502错误,严重阻塞了团 ...

  5. IDEA错误: 找不到或无法加载主类

    错误: 找不到或无法加载主类 idea本身缓存问题 解决:清理缓存重启IDEA file-->invalidate Cache/restart 之后再重新build.

  6. 2019年全网最热门的123个Java并发面试题总结

    前言 并发编程几乎是所有互联网公司面试必问的问题,并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能.它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰.思维缜密, ...

  7. TensorFlow——TensorBoard可视化

    TensorFlow提供了一个可视化工具TensorBoard,它能够将训练过程中的各种绘制数据进行展示出来,包括标量,图片,音频,计算图,数据分布,直方图等,通过网页来观察模型的结构和训练过程中各个 ...

  8. TensorFlow——LinearRegression简单模型代码

    代码函数详解 tf.random.truncated_normal()函数 tf.truncated_normal函数随机生成正态分布的数据,生成的数据是截断的正态分布,截断的标准是2倍的stddev ...

  9. C++中全排列函数next_permutation 用法

    今天蓝桥杯刷题时发现一道字符串排序问题,突然想起next_permutation()函数和prev_permutation()函数. 就想写下next_permutation()的用法 next_pe ...

  10. cogs 1829. [Tyvj 1728]普通平衡树 权值线段树

    1829. [Tyvj 1728]普通平衡树 ★★★   输入文件:phs.in   输出文件:phs.out   简单对比时间限制:1 s   内存限制:1000 MB [题目描述] 您需要写一种数 ...