前言:开闭原则一直是软件开发领域中所追求的,开闭原则中的"开"是指对于组件功能的扩展是开放的,是容许对其进行功能扩展的,“闭”,是指对于原有代码的修改是封闭的,即不该该修改原有的代码。对于一个高度集成化的、成熟、稳健的系统来说,永远不是封闭、固守的,它须要向外提供必定的可扩展的能力,外部的实现类或者jar包均可以调用它。在面向对象的开发领域中,接口是对系统功能的高度抽象,由于SPI可谓是"应运而生",本篇博客就开始走进SPI,探究java自身的SPI和Dubbo的SPI究竟是什么原理java
目录mysql
一:SPI是什么sql
二:jdk的SPI数据库
三:dubbo的SPI缓存
四:总结安全
正文oracle
一:SPI是什么?app
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基础上主要有如下的改变,①配置文件采用键值对配置的方式,使用起来更加灵活和简单 ② 加强了本来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只有全路径名;
Dubbo经过注解@Adaptive做为标记实现了一个适配器类,dubbo将会为这个类动态生成代理对象;ExtensionLoader中获取默认实现类或者经过实现类名称(由@SPI注解指定的名称)来获取实现类
为何会出现@Adaptive这个注解呢?主要缘由是由于dubbo的加载扩展了是从配置文件加载的,是很动态的,可是实现类却要固定写死或者灵活实现,因此就得区分开。用@Adaptive就是表示由框架本身生成,不须要人为实现.
在dubbo加载SPI时会动态建立SPI Adaptive
实现ExtensionLoader
。从URL获取密钥,该密钥将经过@Adaptive由
接口方法定义的注释提供
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文件
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机制能够灵活地实现厂商的规范订制和不一样企业的具体规范本身实现.高度扩展了原程序,使得咱们设计出来的程序更加具备扩展力。