前言:开闭原则一直是软件开发领域中所追求的,开闭原则中的"开"是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的,“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代码。对于一个高度集成化的、成熟、稳健的系统来讲,永远不是封闭、固守的,它需要向外提供一定的可扩展的能力,外部的实现类或者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. mysql授权用户权限

    mysql 用户管理和权限设置   用户管理 mysql>use mysql; 查看 mysql> select host,user,password from user ; 创建 mys ...

  2. 洛谷$P1935$ [国家集训队]圈地计划 网络流

    正解:最小割 解题报告: 传送门 就文理分科模型嘛$QwQ$?所以就,跑个最小割呗,然后就做完辣?仔细想想细节发现并麻油那么简单嗷$QwQ$ 先考虑如果没有这个$k\cdot C_{i,j}$的贡献就 ...

  3. 洛谷$P3302$ 森林 $[SDOI2013]$ 主席树

    正解:主席树 解题报告: 传送门! 口胡一时爽代码火葬场 这题想法不难,,,但显然的是代码应该还挺难打的 但反正我也不放代码,就写下题解趴$QwQ$ 第一问就是个$Count\ on\ a\ tree ...

  4. Linux学习之路--常用命令

    #ls  显示文件信息 #ll  显示文件(不包括隐藏文件)具体信息 等于 #ls -l #ll -a 显示所有文件(包括隐藏文件)具体信息 #ll -htr aa  显示最近修改的文件 h是易读的 ...

  5. 用python做推荐系统(二)

    一.简介 继上一篇基于用户的推荐算法,这一篇是要基于商品的,基于用户的好处是可以根据用户的评价记录找出跟他兴趣相似的用户,再推荐这些用户也喜欢的电影,但是万一这个用户是新用户呢?或是他还没有对任何电影 ...

  6. Python 生成器和协程使用示例

    一.生成器的创建及使用 生成器比迭代器更节省内存空间,使用生成器,可以生成一个值的序列用于迭代,并且这个值的序列不是一次生成的,而是使用一个,再生成一个,的确可以使程序节省大量的内存损耗 创建生成器, ...

  7. React-router路由4.0版本用法

    第一步:引包两个 命令:cnpm i -S react-router react-router-dom 第二步:用路由管理APP页面 1.创建主路由管理页面,在这里使用了路由嵌套 import Rea ...

  8. php5升php7代码修改整理

    1. 为什么升级至php7 a.安全需要 过早以至于不维护的php版本难免会有未修补的漏洞,安全性要求较高的行业是要及时升级的. b.性能提升(开启opcache hugepage) 据说性能提升一倍 ...

  9. 有哪些让人相见恨晚的Python库(一)

    对于我这个经常用python倒腾数据的人来说,下面这个库是真·相见恨晚 记得有一次我在服务器上处理数据时,为了解决Pandas读取超过2000W条数据就内存爆炸的问题,整整用了两天时间来优化.最后通过 ...

  10. 巧用位运算规律 Flags

    找规律 (1 ) &1 =1 (1 ) &2 =0 (1 ) &3 =1 (1 ) &4 =0 (1 ) &5 =1 (1 ) &6 =0 (1 ) & ...